Skip to content

Commit

Permalink
Containers: provide interfaces for conversion from/to external reprs.
Browse files Browse the repository at this point in the history
Similarly to what's done in Magnum::Math, but here for linear
containers.
  • Loading branch information
mosra committed Feb 15, 2019
1 parent e671f43 commit 01c473a
Show file tree
Hide file tree
Showing 9 changed files with 642 additions and 7 deletions.
12 changes: 12 additions & 0 deletions src/Corrade/Containers/Array.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,18 @@ class Array {
/** @brief Move assignment */
Array<T, D>& operator=(Array<T, D>&&) noexcept;

/**
* @brief Convert to external view representation
*/
template<class U, class = decltype(Implementation::ArrayViewConverter<T, U>::to(std::declval<ArrayView<T>>()))> /*implicit*/ operator U() {
return Implementation::ArrayViewConverter<T, U>::to(*this);
}

/** @overload */
template<class U, class = decltype(Implementation::ArrayViewConverter<const T, U>::to(std::declval<ArrayView<const T>>()))> constexpr /*implicit*/ operator U() const {
return Implementation::ArrayViewConverter<const T, U>::to(*this);
}

#ifndef CORRADE_MSVC2017_COMPATIBILITY
/** @brief Whether the array is non-empty */
/* Disabled on MSVC <= 2017 to avoid ambiguous operator+() when doing
Expand Down
82 changes: 82 additions & 0 deletions src/Corrade/Containers/ArrayView.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@

namespace Corrade { namespace Containers {

namespace Implementation {
template<class, class> struct ArrayViewConverter;
template<class> struct ErasedArrayViewConverter;
}

/**
@brief Array view with size information
Expand Down Expand Up @@ -131,6 +136,27 @@ template<class T> class ArrayView {
static_assert(sizeof(U) == sizeof(T), "type sizes are not compatible");
}

/**
* @brief Construct a view on an external type / from an external representation
*/
/* There's no restriction that would disallow creating ArrayView from
e.g. std::vector<T>&& because that would break uses like
`consume(foo());`, where `consume()` expects a view but `foo()`
returns a std::vector. Besides that, to simplify the implementation,
there's no const-adding conversion. Instead, the implementer is
supposed to add an ArrayViewConverter variant for that. */
template<class U, class = decltype(Implementation::ArrayViewConverter<T, typename std::decay<U&&>::type>::from(std::declval<U&&>()))> constexpr /*implicit*/ ArrayView(U&& other) noexcept: ArrayView{Implementation::ArrayViewConverter<T, typename std::decay<U&&>::type>::from(std::forward<U>(other))} {}

/**
* @brief Convert the view to external representation
*/
/* To simplify the implementation, there's no const-adding conversion.
Instead, the implementer is supposed to add an ArrayViewConverter
variant for that. */
template<class U, class = decltype(Implementation::ArrayViewConverter<T, U>::to(std::declval<ArrayView<T>>()))> constexpr /*implicit*/ operator U() const {
return Implementation::ArrayViewConverter<T, U>::to(*this);
}

#ifndef CORRADE_MSVC2017_COMPATIBILITY
/** @brief Whether the array is non-empty */
/* Disabled on MSVC <= 2017 to avoid ambiguous operator+() when doing
Expand Down Expand Up @@ -315,6 +341,15 @@ template<> class ArrayView<const void> {
/** @brief Construct const void view on any @ref StaticArrayView */
template<std::size_t size, class T> constexpr /*implicit*/ ArrayView(const StaticArrayView<size, T>& array) noexcept: _data{array}, _size{size*sizeof(T)} {}

/**
* @brief Construct a view on an external type
*/
/* There's no restriction that would disallow creating ArrayView from
e.g. std::vector<T>&& because that would break uses like
`consume(foo());`, where `consume()` expects a view but `foo()`
returns a std::vector. */
template<class T, class = decltype(Implementation::ErasedArrayViewConverter<const T>::from(std::declval<const T&>()))> constexpr /*implicit*/ ArrayView(const T& other) noexcept: ArrayView{Implementation::ErasedArrayViewConverter<const T>::from(other)} {}

#ifndef CORRADE_MSVC2017_COMPATIBILITY
/** @brief Whether the array is non-empty */
/* Disabled on MSVC <= 2017 to avoid ambiguous operator+() when doing
Expand Down Expand Up @@ -385,6 +420,16 @@ template<class T> constexpr ArrayView<T> arrayView(ArrayView<T> view) {
return view;
}

/** @relatesalso ArrayView
@brief Make a view on an external type / from an external representation
*/
/* There's no restriction that would disallow creating ArrayView from
e.g. std::vector<T>&& because that would break uses like `consume(foo());`,
where `consume()` expects a view but `foo()` returns a std::vector. */
template<class T, class U = decltype(Implementation::ErasedArrayViewConverter<typename std::remove_reference<T&&>::type>::from(std::declval<T&&>()))> constexpr U arrayView(T&& other) {
return Implementation::ErasedArrayViewConverter<typename std::remove_reference<T&&>::type>::from(std::forward<T>(other));
}

/** @relatesalso ArrayView
@brief Reinterpret-cast an array view
Expand Down Expand Up @@ -424,6 +469,11 @@ template<std::size_t size_, class T> constexpr std::size_t arraySize(T(&)[size_]
return size_;
}

namespace Implementation {
template<std::size_t, class, class> struct StaticArrayViewConverter;
template<class> struct ErasedStaticArrayViewConverter;
}

/**
@brief Fixed-size array view
Expand Down Expand Up @@ -499,6 +549,28 @@ template<std::size_t size_, class T> class StaticArrayView {
static_assert(sizeof(T) == sizeof(U), "type sizes are not compatible");
}

/**
* @brief Construct a view on an external type / from an external representation
*/
/* There's no restriction that would disallow creating ArrayView from
e.g. std::vector<T>&& because that would break uses like
`consume(foo());`, where `consume()` expects a view but `foo()`
returns a std::vector. Besides that, to simplify the implementation,
there's no const-adding conversion. Instead, the implementer is
supposed to add a StaticArrayViewConverter variant for that. */
template<class U, class = decltype(Implementation::StaticArrayViewConverter<size_, T, typename std::decay<U&&>::type>::from(std::declval<U&&>()))> constexpr /*implicit*/ StaticArrayView(U&& other) noexcept: StaticArrayView{Implementation::StaticArrayViewConverter<size_, T, typename std::decay<U&&>::type>::from(std::forward<U>(other))} {}

/**
* @brief Convert the view to external representation
*/
/* To simplify the implementation, there's no ArrayViewConverter
overload. Instead, the implementer is supposed to extend
StaticArrayViewConverter specializations for the non-static arrays
as well. The same goes for const-adding conversions. */
template<class U, class = decltype(Implementation::StaticArrayViewConverter<size_, T, U>::to(std::declval<StaticArrayView<size_, T>>()))> constexpr /*implicit*/ operator U() const {
return Implementation::StaticArrayViewConverter<size_, T, U>::to(*this);
}

#ifndef CORRADE_MSVC2017_COMPATIBILITY
/** @brief Whether the array is non-empty */
/* Disabled on MSVC <= 2017 to avoid ambiguous operator+() when doing
Expand Down Expand Up @@ -632,6 +704,16 @@ template<std::size_t size, class T> constexpr StaticArrayView<size, T> staticArr
return view;
}

/** @relatesalso StaticArrayView
@brief Make a static view on an external type / from an external representation
*/
/* There's no restriction that would disallow creating StaticArrayView from
e.g. std::array<T>&& because that would break uses like `consume(foo());`,
where `consume()` expects a view but `foo()` returns a std::array. */
template<class T, class U = decltype(Implementation::ErasedStaticArrayViewConverter<typename std::remove_reference<T&&>::type>::from(std::declval<T&&>()))> constexpr U staticArrayView(T&& other) {
return Implementation::ErasedStaticArrayViewConverter<typename std::remove_reference<T&&>::type>::from(std::forward<T>(other));
}

/** @relatesalso StaticArrayView
@brief Reinterpret-cast a static array view
Expand Down
20 changes: 18 additions & 2 deletions src/Corrade/Containers/StaticArray.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,24 @@ template<std::size_t size_, class T> class StaticArray {
return StaticArrayView<size_, const U>{_data};
}

/* `char* a = Containers::Array<char>(5); a[3] = 5;` would result in
instant segfault, disallowing it in the following conversion
/**
* @brief Convert to external view representation
*/
/* To simplify the implementation, there's no ArrayViewConverter
overload. Instead, the implementer is supposed to extend
StaticArrayViewConverter specializations for the non-static arrays
as well. */
template<class U, class = decltype(Implementation::StaticArrayViewConverter<size_, T, U>::to(std::declval<StaticArrayView<size_, T>>()))> /*implicit*/ operator U() {
return Implementation::StaticArrayViewConverter<size_, T, U>::to(*this);
}

/** @overload */
template<class U, class = decltype(Implementation::StaticArrayViewConverter<size_, const T, U>::to(std::declval<StaticArrayView<size_, const T>>()))> constexpr /*implicit*/ operator U() const {
return Implementation::StaticArrayViewConverter<size_, const T, U>::to(*this);
}

/* `char* a = Containers::StaticArray<char>(5); a[3] = 5;` would result
in instant segfault, disallowing it in the following conversion
operators */

/** @brief Conversion to array type */
Expand Down
12 changes: 12 additions & 0 deletions src/Corrade/Containers/StridedArrayView.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,18 @@ template<class T> class StridedArrayView {
static_assert(sizeof(U) == sizeof(T), "type sizes are not compatible");
}

/**
* @brief Construct a view from an external view representation
*/
/* There's no restriction that would disallow creating StridedArrayView
from e.g. std::vector<T>&& because that would break uses like
`consume(foo());`, where `consume()` expects a view but `foo()`
returns a std::vector. Besides that, there's no
StaticArrayViewConverter overload as we wouldn't be able to infer
the size parameter. Since ArrayViewConverter is supposed to handle
conversion from statically sized arrays as well, this is okay. */
template<class U, class = decltype(Implementation::ArrayViewConverter<T, typename std::decay<U&&>::type>::from(std::declval<U&&>()))> constexpr /*implicit*/ StridedArrayView(U&& other) noexcept: StridedArrayView{Implementation::ArrayViewConverter<T, typename std::decay<U&&>::type>::from(std::forward<U>(other))} {}

/** @brief Whether the array is non-empty */
constexpr explicit operator bool() const { return _data; }

Expand Down
72 changes: 71 additions & 1 deletion src/Corrade/Containers/Test/ArrayTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,43 @@
#include "Corrade/Containers/Array.h"
#include "Corrade/TestSuite/Tester.h"

namespace Corrade { namespace Containers { namespace Test { namespace {
namespace {

struct IntView {
IntView(int* data, std::size_t size): data{data}, size{size} {}

int* data;
std::size_t size;
};

struct ConstIntView {
ConstIntView(const int* data, std::size_t size): data{data}, size{size} {}

const int* data;
std::size_t size;
};

}

namespace Corrade { namespace Containers {

namespace Implementation {

template<> struct ArrayViewConverter<int, IntView> {
static IntView to(ArrayView<int> other) {
return {other.data(), other.size()};
}
};

template<> struct ArrayViewConverter<const int, ConstIntView> {
static ConstIntView to(ArrayView<const int> other) {
return {other.data(), other.size()};
}
};

}

namespace Test { namespace {

struct ArrayTest: TestSuite::Tester {
explicit ArrayTest();
Expand All @@ -51,6 +87,8 @@ struct ArrayTest: TestSuite::Tester {
void convertView();
void convertViewDerived();
void convertVoid();
void convertToExternalView();
void convertToConstExternalView();

void emptyCheck();
void access();
Expand Down Expand Up @@ -98,6 +136,8 @@ ArrayTest::ArrayTest() {
&ArrayTest::convertView,
&ArrayTest::convertViewDerived,
&ArrayTest::convertVoid,
&ArrayTest::convertToExternalView,
&ArrayTest::convertToConstExternalView,

&ArrayTest::emptyCheck,
&ArrayTest::access,
Expand Down Expand Up @@ -356,6 +396,36 @@ void ArrayTest::convertVoid() {
CORRADE_COMPARE(cb.size(), ca.size()*sizeof(int));
}

void ArrayTest::convertToExternalView() {
Array a{InPlaceInit, {1, 2, 3, 4, 5}};

IntView b = a;
CORRADE_COMPARE(b.data, a);
CORRADE_COMPARE(b.size, a.size());

ConstIntView cb = a;
CORRADE_COMPARE(cb.data, a);
CORRADE_COMPARE(cb.size, a.size());

/* Conversion to a different type is not allowed */
CORRADE_VERIFY((std::is_convertible<Containers::Array<int>, IntView>::value));
CORRADE_VERIFY((std::is_convertible<Containers::Array<int>, ConstIntView>::value));
CORRADE_VERIFY(!(std::is_convertible<Containers::Array<float>, IntView>::value));
CORRADE_VERIFY(!(std::is_convertible<Containers::Array<float>, ConstIntView>::value));
}

void ArrayTest::convertToConstExternalView() {
const Array a{InPlaceInit, {1, 2, 3, 4, 5}};

ConstIntView b = a;
CORRADE_COMPARE(b.data, a);
CORRADE_COMPARE(b.size, a.size());

/* Conversion to a different type is not allowed */
CORRADE_VERIFY((std::is_convertible<const Containers::Array<int>, ConstIntView>::value));
CORRADE_VERIFY(!(std::is_convertible<const Containers::Array<float>, ConstIntView>::value));
}

void ArrayTest::emptyCheck() {
Array a;
CORRADE_VERIFY(!a);
Expand Down
Loading

0 comments on commit 01c473a

Please sign in to comment.