C++ std::array wrapper

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
8
down vote

favorite
2












I implemented a std::array wrapper which primarily adds various constructors, since std::array has no explicit constructors itself, but rather uses aggregate initialization.



I like to have some feedback on my code which heavily depends on template meta-programming. More particularly:



  • Are there still cases where I can exploit move semantics or where I will unnecessarily copy large values (which can become a problem for large array elements)?

  • Are there still cases where I can use more stringent conditions for enabling methods (i.e. SFINAE)? (e.g. type deduction/decaying of tuple elements).

  • Are there elegant strategies for supporting Arrays containing only one element (or even no elements at all)? (Note the potential conflicts with the copy and move constructor. Array needs to be capable of handling pointer elements as well in the presence of inheritance. Furthermore, Array will act as a base class in my code base.)?

  • Is it possible to chain Array constructors instead of always immediately redirect to std::array itself?

  • General guidelines, best practices?

Try It Online



Includes:



#include <array>
#include <iostream>
#include <tuple>
#include <utility>


Array Utilities:



namespace details 

template< typename ActionT, typename FromT, size_t...I >
constexpr decltype(auto) TransformArray(ActionT action,
const std::array< FromT, sizeof...(I) >& a,
std::index_sequence< I... >)

using ToT = decltype(std::declval< ActionT >()(std::declval< FromT >()));
return std::array< ToT, sizeof...(I) > action(a[I])... ;


template< typename T, size_t...I >
constexpr decltype(auto) FillArray(T value, std::index_sequence< I... >)
return std::array< T, sizeof...(I) > (static_cast< void >(I), value)... ;


template< size_t ToN, typename T, size_t...I >
constexpr decltype(auto) EnlargeArray(const std::array< T, sizeof...(I) >& a,
std::index_sequence< I... >)

return std::array< T, ToN > a[I]... ;


template< typename T, typename TupleT, std::size_t... I >
constexpr decltype(auto) TuppleToArray(const TupleT& t,
std::index_sequence< I... >)

return std::array< T, sizeof...(I) > std::get< I >(t)... ;



template< typename ActionT, typename FromT, size_t N >
constexpr decltype(auto) TransformArray(ActionT action,
const std::array< FromT, N >& a)

return details::TransformArray(std::move(action), a,
std::make_index_sequence< N >());


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) StaticCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return static_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) DynamicCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return dynamic_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) ConstCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return const_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) ReinterpretCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return reinterpret_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename T, size_t N >
constexpr decltype(auto) FillArray(T value)
return details::FillArray(value, std::make_index_sequence< N >());


template< size_t ToN, typename T, size_t FromN >
constexpr decltype(auto) EnlargeArray(const std::array< T, FromN >& a)
return details::EnlargeArray< ToN >(a, std::make_index_sequence< FromN >());


template< typename T, typename... Ts >
constexpr decltype(auto) TuppleToArray(const std::tuple< T, Ts... >& t)
constexpr auto N = sizeof...(Ts) + 1u;
return details::TuppleToArray< T >(t, std::make_index_sequence< N >());



Tuple Utilities:



namespace details 

template< typename T, size_t...I >
constexpr decltype(auto) ArrayToTupple(const std::array< T, sizeof...(I) >& a,
std::index_sequence< I... >) noexcept
return std::make_tuple(a[I]...);



template< typename T, size_t N >
constexpr decltype(auto) ArrayToTupple(const std::array< T, N >& a) noexcept
return details::ArrayToTupple(a, std::make_index_sequence< N >());


template< typename... ArgsT >
constexpr decltype(auto) ArgsToTuple(ArgsT&&... args) noexcept
return std::make_tuple(std::forward< ArgsT >(args)...);



Array wrapper:



template< typename T, size_t N, 
typename = std::enable_if_t< (N > 1) > >
struct Array : std::array< T, N >

constexpr Array() noexcept
: std::array< T, N >

template< typename... ArgsT,
typename = std::enable_if_t< (N == sizeof...(ArgsT)) > >
constexpr Array(ArgsT&&... args) noexcept
: std::array< T, N > std::forward< ArgsT >(args)...

template< size_t FromN,
typename = std::enable_if_t< (FromN < N) > >
constexpr Array(const Array< T, FromN >& a) noexcept
: std::array< T, N >(EnlargeArray< N >(a))

template< size_t FromN, typename... ArgsT,
typename = std::enable_if_t< (FromN < N && (FromN + sizeof...(ArgsT)) == N) > >
constexpr Array(const Array< T, FromN >& a, ArgsT&&... args) noexcept
: std::array< T, N >(TuppleToArray(
std::tuple_cat(ArrayToTupple(a), ArgsToTuple(std::forward< ArgsT >(args)...))))

constexpr Array(const Array& a) noexcept = default;

constexpr Array(Array&& a) noexcept = default;

template< typename U >
constexpr explicit Array(const Array< U, N >& a) noexcept
: std::array< T, N >(StaticCastArray< T >(a))

~Array() = default;

constexpr Array& operator=(const Array& a) noexcept = default;

constexpr Array& operator=(Array&& a) noexcept = default;

// It would be nice to have properties in C++ (supported in msvc++ and Clang).

constexpr std::enable_if_t< ( 1 <= N ), T& > GetX() noexcept
return std::array< T, N >::operator(0);

constexpr std::enable_if_t< ( 2 <= N ), T& > GetY() noexcept
return std::array< T, N >::operator(1);

constexpr std::enable_if_t< ( 3 <= N ), T& > GetZ() noexcept
return std::array< T, N >::operator(2);

constexpr std::enable_if_t< ( 4 <= N ), T& > GetW() noexcept
return std::array< T, N >::operator(3);


constexpr std::enable_if_t< ( 1 <= N ), const T& > GetX() const noexcept
return std::array< T, N >::operator(0);

constexpr std::enable_if_t< ( 2 <= N ), const T& > GetY() const noexcept
return std::array< T, N >::operator(1);

constexpr std::enable_if_t< ( 3 <= N ), const T& > GetZ() const noexcept
return std::array< T, N >::operator(2);

constexpr std::enable_if_t< ( 4 <= N ), const T& > GetW() const noexcept
return std::array< T, N >::operator(3);

;


Some extra utilities for illustration purposes:



template< typename T, std::size_t N >
std::ostream& operator<<(std::ostream& os, const std::array< T, N >& a)
for (auto i : a) os << i << ' ';
return os << 'n';


int main()
constexpr Array< float, 5 > a;
std::cout << a;

constexpr Array< float, 5 > b( 1.5f, 2.5f, 3.5f, 4.5f, 5.5f );
std::cout << b;

constexpr Array< float, 5 > c 1.5f, 2.5f, 3.5f, 4.5f, 5.5f ;
std::cout << c;

constexpr Array< float, 6 > d(c);
std::cout << d;

constexpr Array< float, 6 > e(c, 6.5f);
std::cout << e;

constexpr Array< int, 6 > f(e);
std::cout << f;

return 0;



Edit 1:
Try It Online




  • auto instead of decltype(auto) (all methods return by value) (thanks to Incomputable)

  • Universal reference for ActionT + perfect forwarding of ActionT (thanks to Incomputable)

Edit 2:
Try It Online



  • All accessor/getter member methods are removed from Array, since they are not applicable to all possible derived classes of Array. A RGB color spectrum does not have X,Y,Z components, whereas a XYZ color spectrum does. A UVW normalized 3D texture position has its W component at index 2, whereas a 4D homogeneous position has its W component at index 3. Derived classes can implement these accessor/getter member methods themselves or inherit from pure abstract classes (as mentioned and illustrated by Incomputable). Furthermore, it is possible to add a size_t Index template argument to specify the right index. Depending on the size of T, one can also return by value for small values, and by const reference for large values. Depending on the type of T, one can use different call conventions as well (e.g. __vectorcall, __fastcall, etc.).


  • ArgsToTupple is removed, since it is just a wrapper around std::make_tupple.

  • An extra alignment template argument A is added to Array. Furthermore, some extra explicit constructors are added to support converting between Array instances with a different alignment.

Edit 3:
Try It Online



  • Added a constructor for replicating a single value. This makes sense in the absence of Arrays of at most one element. I personally see no use case for a std::array< T, 0 > or std::array< T, 1 > either, given that the number of elements needs to be known at compile time.

  • Added a std::is_convertible_v type trait to construct an Array from a given Array containing elements of a different type. This enables the construction of an Array< Array< T, N1 >, N2 > by replicating a single Array< T, N1 > using the newly added constructor.






share|improve this question

















  • 1




    Great. Now make a github repo and share it with the world.
    – FreezePhoenix
    Apr 30 at 18:30










  • @Pheo: Github: just the sample | Github: larger project using the sample ;)
    – Matthias
    Apr 30 at 18:46











  • Um... Dang. That's a big project.
    – FreezePhoenix
    Apr 30 at 18:52






  • 1




    @Matthias, I recommend waiting a little bit before accepting an answer. People feel discouraged to post their own if one is already accepted. One or two days is usually a good time period.
    – Incomputable
    Apr 30 at 19:39










  • @Incomputable Ok, I will postpone accepting an answer.
    – Matthias
    Apr 30 at 19:41
















up vote
8
down vote

favorite
2












I implemented a std::array wrapper which primarily adds various constructors, since std::array has no explicit constructors itself, but rather uses aggregate initialization.



I like to have some feedback on my code which heavily depends on template meta-programming. More particularly:



  • Are there still cases where I can exploit move semantics or where I will unnecessarily copy large values (which can become a problem for large array elements)?

  • Are there still cases where I can use more stringent conditions for enabling methods (i.e. SFINAE)? (e.g. type deduction/decaying of tuple elements).

  • Are there elegant strategies for supporting Arrays containing only one element (or even no elements at all)? (Note the potential conflicts with the copy and move constructor. Array needs to be capable of handling pointer elements as well in the presence of inheritance. Furthermore, Array will act as a base class in my code base.)?

  • Is it possible to chain Array constructors instead of always immediately redirect to std::array itself?

  • General guidelines, best practices?

Try It Online



Includes:



#include <array>
#include <iostream>
#include <tuple>
#include <utility>


Array Utilities:



namespace details 

template< typename ActionT, typename FromT, size_t...I >
constexpr decltype(auto) TransformArray(ActionT action,
const std::array< FromT, sizeof...(I) >& a,
std::index_sequence< I... >)

using ToT = decltype(std::declval< ActionT >()(std::declval< FromT >()));
return std::array< ToT, sizeof...(I) > action(a[I])... ;


template< typename T, size_t...I >
constexpr decltype(auto) FillArray(T value, std::index_sequence< I... >)
return std::array< T, sizeof...(I) > (static_cast< void >(I), value)... ;


template< size_t ToN, typename T, size_t...I >
constexpr decltype(auto) EnlargeArray(const std::array< T, sizeof...(I) >& a,
std::index_sequence< I... >)

return std::array< T, ToN > a[I]... ;


template< typename T, typename TupleT, std::size_t... I >
constexpr decltype(auto) TuppleToArray(const TupleT& t,
std::index_sequence< I... >)

return std::array< T, sizeof...(I) > std::get< I >(t)... ;



template< typename ActionT, typename FromT, size_t N >
constexpr decltype(auto) TransformArray(ActionT action,
const std::array< FromT, N >& a)

return details::TransformArray(std::move(action), a,
std::make_index_sequence< N >());


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) StaticCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return static_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) DynamicCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return dynamic_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) ConstCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return const_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) ReinterpretCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return reinterpret_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename T, size_t N >
constexpr decltype(auto) FillArray(T value)
return details::FillArray(value, std::make_index_sequence< N >());


template< size_t ToN, typename T, size_t FromN >
constexpr decltype(auto) EnlargeArray(const std::array< T, FromN >& a)
return details::EnlargeArray< ToN >(a, std::make_index_sequence< FromN >());


template< typename T, typename... Ts >
constexpr decltype(auto) TuppleToArray(const std::tuple< T, Ts... >& t)
constexpr auto N = sizeof...(Ts) + 1u;
return details::TuppleToArray< T >(t, std::make_index_sequence< N >());



Tuple Utilities:



namespace details 

template< typename T, size_t...I >
constexpr decltype(auto) ArrayToTupple(const std::array< T, sizeof...(I) >& a,
std::index_sequence< I... >) noexcept
return std::make_tuple(a[I]...);



template< typename T, size_t N >
constexpr decltype(auto) ArrayToTupple(const std::array< T, N >& a) noexcept
return details::ArrayToTupple(a, std::make_index_sequence< N >());


template< typename... ArgsT >
constexpr decltype(auto) ArgsToTuple(ArgsT&&... args) noexcept
return std::make_tuple(std::forward< ArgsT >(args)...);



Array wrapper:



template< typename T, size_t N, 
typename = std::enable_if_t< (N > 1) > >
struct Array : std::array< T, N >

constexpr Array() noexcept
: std::array< T, N >

template< typename... ArgsT,
typename = std::enable_if_t< (N == sizeof...(ArgsT)) > >
constexpr Array(ArgsT&&... args) noexcept
: std::array< T, N > std::forward< ArgsT >(args)...

template< size_t FromN,
typename = std::enable_if_t< (FromN < N) > >
constexpr Array(const Array< T, FromN >& a) noexcept
: std::array< T, N >(EnlargeArray< N >(a))

template< size_t FromN, typename... ArgsT,
typename = std::enable_if_t< (FromN < N && (FromN + sizeof...(ArgsT)) == N) > >
constexpr Array(const Array< T, FromN >& a, ArgsT&&... args) noexcept
: std::array< T, N >(TuppleToArray(
std::tuple_cat(ArrayToTupple(a), ArgsToTuple(std::forward< ArgsT >(args)...))))

constexpr Array(const Array& a) noexcept = default;

constexpr Array(Array&& a) noexcept = default;

template< typename U >
constexpr explicit Array(const Array< U, N >& a) noexcept
: std::array< T, N >(StaticCastArray< T >(a))

~Array() = default;

constexpr Array& operator=(const Array& a) noexcept = default;

constexpr Array& operator=(Array&& a) noexcept = default;

// It would be nice to have properties in C++ (supported in msvc++ and Clang).

constexpr std::enable_if_t< ( 1 <= N ), T& > GetX() noexcept
return std::array< T, N >::operator(0);

constexpr std::enable_if_t< ( 2 <= N ), T& > GetY() noexcept
return std::array< T, N >::operator(1);

constexpr std::enable_if_t< ( 3 <= N ), T& > GetZ() noexcept
return std::array< T, N >::operator(2);

constexpr std::enable_if_t< ( 4 <= N ), T& > GetW() noexcept
return std::array< T, N >::operator(3);


constexpr std::enable_if_t< ( 1 <= N ), const T& > GetX() const noexcept
return std::array< T, N >::operator(0);

constexpr std::enable_if_t< ( 2 <= N ), const T& > GetY() const noexcept
return std::array< T, N >::operator(1);

constexpr std::enable_if_t< ( 3 <= N ), const T& > GetZ() const noexcept
return std::array< T, N >::operator(2);

constexpr std::enable_if_t< ( 4 <= N ), const T& > GetW() const noexcept
return std::array< T, N >::operator(3);

;


Some extra utilities for illustration purposes:



template< typename T, std::size_t N >
std::ostream& operator<<(std::ostream& os, const std::array< T, N >& a)
for (auto i : a) os << i << ' ';
return os << 'n';


int main()
constexpr Array< float, 5 > a;
std::cout << a;

constexpr Array< float, 5 > b( 1.5f, 2.5f, 3.5f, 4.5f, 5.5f );
std::cout << b;

constexpr Array< float, 5 > c 1.5f, 2.5f, 3.5f, 4.5f, 5.5f ;
std::cout << c;

constexpr Array< float, 6 > d(c);
std::cout << d;

constexpr Array< float, 6 > e(c, 6.5f);
std::cout << e;

constexpr Array< int, 6 > f(e);
std::cout << f;

return 0;



Edit 1:
Try It Online




  • auto instead of decltype(auto) (all methods return by value) (thanks to Incomputable)

  • Universal reference for ActionT + perfect forwarding of ActionT (thanks to Incomputable)

Edit 2:
Try It Online



  • All accessor/getter member methods are removed from Array, since they are not applicable to all possible derived classes of Array. A RGB color spectrum does not have X,Y,Z components, whereas a XYZ color spectrum does. A UVW normalized 3D texture position has its W component at index 2, whereas a 4D homogeneous position has its W component at index 3. Derived classes can implement these accessor/getter member methods themselves or inherit from pure abstract classes (as mentioned and illustrated by Incomputable). Furthermore, it is possible to add a size_t Index template argument to specify the right index. Depending on the size of T, one can also return by value for small values, and by const reference for large values. Depending on the type of T, one can use different call conventions as well (e.g. __vectorcall, __fastcall, etc.).


  • ArgsToTupple is removed, since it is just a wrapper around std::make_tupple.

  • An extra alignment template argument A is added to Array. Furthermore, some extra explicit constructors are added to support converting between Array instances with a different alignment.

Edit 3:
Try It Online



  • Added a constructor for replicating a single value. This makes sense in the absence of Arrays of at most one element. I personally see no use case for a std::array< T, 0 > or std::array< T, 1 > either, given that the number of elements needs to be known at compile time.

  • Added a std::is_convertible_v type trait to construct an Array from a given Array containing elements of a different type. This enables the construction of an Array< Array< T, N1 >, N2 > by replicating a single Array< T, N1 > using the newly added constructor.






share|improve this question

















  • 1




    Great. Now make a github repo and share it with the world.
    – FreezePhoenix
    Apr 30 at 18:30










  • @Pheo: Github: just the sample | Github: larger project using the sample ;)
    – Matthias
    Apr 30 at 18:46











  • Um... Dang. That's a big project.
    – FreezePhoenix
    Apr 30 at 18:52






  • 1




    @Matthias, I recommend waiting a little bit before accepting an answer. People feel discouraged to post their own if one is already accepted. One or two days is usually a good time period.
    – Incomputable
    Apr 30 at 19:39










  • @Incomputable Ok, I will postpone accepting an answer.
    – Matthias
    Apr 30 at 19:41












up vote
8
down vote

favorite
2









up vote
8
down vote

favorite
2






2





I implemented a std::array wrapper which primarily adds various constructors, since std::array has no explicit constructors itself, but rather uses aggregate initialization.



I like to have some feedback on my code which heavily depends on template meta-programming. More particularly:



  • Are there still cases where I can exploit move semantics or where I will unnecessarily copy large values (which can become a problem for large array elements)?

  • Are there still cases where I can use more stringent conditions for enabling methods (i.e. SFINAE)? (e.g. type deduction/decaying of tuple elements).

  • Are there elegant strategies for supporting Arrays containing only one element (or even no elements at all)? (Note the potential conflicts with the copy and move constructor. Array needs to be capable of handling pointer elements as well in the presence of inheritance. Furthermore, Array will act as a base class in my code base.)?

  • Is it possible to chain Array constructors instead of always immediately redirect to std::array itself?

  • General guidelines, best practices?

Try It Online



Includes:



#include <array>
#include <iostream>
#include <tuple>
#include <utility>


Array Utilities:



namespace details 

template< typename ActionT, typename FromT, size_t...I >
constexpr decltype(auto) TransformArray(ActionT action,
const std::array< FromT, sizeof...(I) >& a,
std::index_sequence< I... >)

using ToT = decltype(std::declval< ActionT >()(std::declval< FromT >()));
return std::array< ToT, sizeof...(I) > action(a[I])... ;


template< typename T, size_t...I >
constexpr decltype(auto) FillArray(T value, std::index_sequence< I... >)
return std::array< T, sizeof...(I) > (static_cast< void >(I), value)... ;


template< size_t ToN, typename T, size_t...I >
constexpr decltype(auto) EnlargeArray(const std::array< T, sizeof...(I) >& a,
std::index_sequence< I... >)

return std::array< T, ToN > a[I]... ;


template< typename T, typename TupleT, std::size_t... I >
constexpr decltype(auto) TuppleToArray(const TupleT& t,
std::index_sequence< I... >)

return std::array< T, sizeof...(I) > std::get< I >(t)... ;



template< typename ActionT, typename FromT, size_t N >
constexpr decltype(auto) TransformArray(ActionT action,
const std::array< FromT, N >& a)

return details::TransformArray(std::move(action), a,
std::make_index_sequence< N >());


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) StaticCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return static_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) DynamicCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return dynamic_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) ConstCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return const_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) ReinterpretCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return reinterpret_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename T, size_t N >
constexpr decltype(auto) FillArray(T value)
return details::FillArray(value, std::make_index_sequence< N >());


template< size_t ToN, typename T, size_t FromN >
constexpr decltype(auto) EnlargeArray(const std::array< T, FromN >& a)
return details::EnlargeArray< ToN >(a, std::make_index_sequence< FromN >());


template< typename T, typename... Ts >
constexpr decltype(auto) TuppleToArray(const std::tuple< T, Ts... >& t)
constexpr auto N = sizeof...(Ts) + 1u;
return details::TuppleToArray< T >(t, std::make_index_sequence< N >());



Tuple Utilities:



namespace details 

template< typename T, size_t...I >
constexpr decltype(auto) ArrayToTupple(const std::array< T, sizeof...(I) >& a,
std::index_sequence< I... >) noexcept
return std::make_tuple(a[I]...);



template< typename T, size_t N >
constexpr decltype(auto) ArrayToTupple(const std::array< T, N >& a) noexcept
return details::ArrayToTupple(a, std::make_index_sequence< N >());


template< typename... ArgsT >
constexpr decltype(auto) ArgsToTuple(ArgsT&&... args) noexcept
return std::make_tuple(std::forward< ArgsT >(args)...);



Array wrapper:



template< typename T, size_t N, 
typename = std::enable_if_t< (N > 1) > >
struct Array : std::array< T, N >

constexpr Array() noexcept
: std::array< T, N >

template< typename... ArgsT,
typename = std::enable_if_t< (N == sizeof...(ArgsT)) > >
constexpr Array(ArgsT&&... args) noexcept
: std::array< T, N > std::forward< ArgsT >(args)...

template< size_t FromN,
typename = std::enable_if_t< (FromN < N) > >
constexpr Array(const Array< T, FromN >& a) noexcept
: std::array< T, N >(EnlargeArray< N >(a))

template< size_t FromN, typename... ArgsT,
typename = std::enable_if_t< (FromN < N && (FromN + sizeof...(ArgsT)) == N) > >
constexpr Array(const Array< T, FromN >& a, ArgsT&&... args) noexcept
: std::array< T, N >(TuppleToArray(
std::tuple_cat(ArrayToTupple(a), ArgsToTuple(std::forward< ArgsT >(args)...))))

constexpr Array(const Array& a) noexcept = default;

constexpr Array(Array&& a) noexcept = default;

template< typename U >
constexpr explicit Array(const Array< U, N >& a) noexcept
: std::array< T, N >(StaticCastArray< T >(a))

~Array() = default;

constexpr Array& operator=(const Array& a) noexcept = default;

constexpr Array& operator=(Array&& a) noexcept = default;

// It would be nice to have properties in C++ (supported in msvc++ and Clang).

constexpr std::enable_if_t< ( 1 <= N ), T& > GetX() noexcept
return std::array< T, N >::operator(0);

constexpr std::enable_if_t< ( 2 <= N ), T& > GetY() noexcept
return std::array< T, N >::operator(1);

constexpr std::enable_if_t< ( 3 <= N ), T& > GetZ() noexcept
return std::array< T, N >::operator(2);

constexpr std::enable_if_t< ( 4 <= N ), T& > GetW() noexcept
return std::array< T, N >::operator(3);


constexpr std::enable_if_t< ( 1 <= N ), const T& > GetX() const noexcept
return std::array< T, N >::operator(0);

constexpr std::enable_if_t< ( 2 <= N ), const T& > GetY() const noexcept
return std::array< T, N >::operator(1);

constexpr std::enable_if_t< ( 3 <= N ), const T& > GetZ() const noexcept
return std::array< T, N >::operator(2);

constexpr std::enable_if_t< ( 4 <= N ), const T& > GetW() const noexcept
return std::array< T, N >::operator(3);

;


Some extra utilities for illustration purposes:



template< typename T, std::size_t N >
std::ostream& operator<<(std::ostream& os, const std::array< T, N >& a)
for (auto i : a) os << i << ' ';
return os << 'n';


int main()
constexpr Array< float, 5 > a;
std::cout << a;

constexpr Array< float, 5 > b( 1.5f, 2.5f, 3.5f, 4.5f, 5.5f );
std::cout << b;

constexpr Array< float, 5 > c 1.5f, 2.5f, 3.5f, 4.5f, 5.5f ;
std::cout << c;

constexpr Array< float, 6 > d(c);
std::cout << d;

constexpr Array< float, 6 > e(c, 6.5f);
std::cout << e;

constexpr Array< int, 6 > f(e);
std::cout << f;

return 0;



Edit 1:
Try It Online




  • auto instead of decltype(auto) (all methods return by value) (thanks to Incomputable)

  • Universal reference for ActionT + perfect forwarding of ActionT (thanks to Incomputable)

Edit 2:
Try It Online



  • All accessor/getter member methods are removed from Array, since they are not applicable to all possible derived classes of Array. A RGB color spectrum does not have X,Y,Z components, whereas a XYZ color spectrum does. A UVW normalized 3D texture position has its W component at index 2, whereas a 4D homogeneous position has its W component at index 3. Derived classes can implement these accessor/getter member methods themselves or inherit from pure abstract classes (as mentioned and illustrated by Incomputable). Furthermore, it is possible to add a size_t Index template argument to specify the right index. Depending on the size of T, one can also return by value for small values, and by const reference for large values. Depending on the type of T, one can use different call conventions as well (e.g. __vectorcall, __fastcall, etc.).


  • ArgsToTupple is removed, since it is just a wrapper around std::make_tupple.

  • An extra alignment template argument A is added to Array. Furthermore, some extra explicit constructors are added to support converting between Array instances with a different alignment.

Edit 3:
Try It Online



  • Added a constructor for replicating a single value. This makes sense in the absence of Arrays of at most one element. I personally see no use case for a std::array< T, 0 > or std::array< T, 1 > either, given that the number of elements needs to be known at compile time.

  • Added a std::is_convertible_v type trait to construct an Array from a given Array containing elements of a different type. This enables the construction of an Array< Array< T, N1 >, N2 > by replicating a single Array< T, N1 > using the newly added constructor.






share|improve this question













I implemented a std::array wrapper which primarily adds various constructors, since std::array has no explicit constructors itself, but rather uses aggregate initialization.



I like to have some feedback on my code which heavily depends on template meta-programming. More particularly:



  • Are there still cases where I can exploit move semantics or where I will unnecessarily copy large values (which can become a problem for large array elements)?

  • Are there still cases where I can use more stringent conditions for enabling methods (i.e. SFINAE)? (e.g. type deduction/decaying of tuple elements).

  • Are there elegant strategies for supporting Arrays containing only one element (or even no elements at all)? (Note the potential conflicts with the copy and move constructor. Array needs to be capable of handling pointer elements as well in the presence of inheritance. Furthermore, Array will act as a base class in my code base.)?

  • Is it possible to chain Array constructors instead of always immediately redirect to std::array itself?

  • General guidelines, best practices?

Try It Online



Includes:



#include <array>
#include <iostream>
#include <tuple>
#include <utility>


Array Utilities:



namespace details 

template< typename ActionT, typename FromT, size_t...I >
constexpr decltype(auto) TransformArray(ActionT action,
const std::array< FromT, sizeof...(I) >& a,
std::index_sequence< I... >)

using ToT = decltype(std::declval< ActionT >()(std::declval< FromT >()));
return std::array< ToT, sizeof...(I) > action(a[I])... ;


template< typename T, size_t...I >
constexpr decltype(auto) FillArray(T value, std::index_sequence< I... >)
return std::array< T, sizeof...(I) > (static_cast< void >(I), value)... ;


template< size_t ToN, typename T, size_t...I >
constexpr decltype(auto) EnlargeArray(const std::array< T, sizeof...(I) >& a,
std::index_sequence< I... >)

return std::array< T, ToN > a[I]... ;


template< typename T, typename TupleT, std::size_t... I >
constexpr decltype(auto) TuppleToArray(const TupleT& t,
std::index_sequence< I... >)

return std::array< T, sizeof...(I) > std::get< I >(t)... ;



template< typename ActionT, typename FromT, size_t N >
constexpr decltype(auto) TransformArray(ActionT action,
const std::array< FromT, N >& a)

return details::TransformArray(std::move(action), a,
std::make_index_sequence< N >());


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) StaticCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return static_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) DynamicCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return dynamic_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) ConstCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return const_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename ToT, typename FromT, size_t N >
constexpr decltype(auto) ReinterpretCastArray(const std::array< FromT, N >& a)
constexpr auto f = (const FromT& v)
return reinterpret_cast< ToT >(v);
;
return TransformArray(f, a);


template< typename T, size_t N >
constexpr decltype(auto) FillArray(T value)
return details::FillArray(value, std::make_index_sequence< N >());


template< size_t ToN, typename T, size_t FromN >
constexpr decltype(auto) EnlargeArray(const std::array< T, FromN >& a)
return details::EnlargeArray< ToN >(a, std::make_index_sequence< FromN >());


template< typename T, typename... Ts >
constexpr decltype(auto) TuppleToArray(const std::tuple< T, Ts... >& t)
constexpr auto N = sizeof...(Ts) + 1u;
return details::TuppleToArray< T >(t, std::make_index_sequence< N >());



Tuple Utilities:



namespace details 

template< typename T, size_t...I >
constexpr decltype(auto) ArrayToTupple(const std::array< T, sizeof...(I) >& a,
std::index_sequence< I... >) noexcept
return std::make_tuple(a[I]...);



template< typename T, size_t N >
constexpr decltype(auto) ArrayToTupple(const std::array< T, N >& a) noexcept
return details::ArrayToTupple(a, std::make_index_sequence< N >());


template< typename... ArgsT >
constexpr decltype(auto) ArgsToTuple(ArgsT&&... args) noexcept
return std::make_tuple(std::forward< ArgsT >(args)...);



Array wrapper:



template< typename T, size_t N, 
typename = std::enable_if_t< (N > 1) > >
struct Array : std::array< T, N >

constexpr Array() noexcept
: std::array< T, N >

template< typename... ArgsT,
typename = std::enable_if_t< (N == sizeof...(ArgsT)) > >
constexpr Array(ArgsT&&... args) noexcept
: std::array< T, N > std::forward< ArgsT >(args)...

template< size_t FromN,
typename = std::enable_if_t< (FromN < N) > >
constexpr Array(const Array< T, FromN >& a) noexcept
: std::array< T, N >(EnlargeArray< N >(a))

template< size_t FromN, typename... ArgsT,
typename = std::enable_if_t< (FromN < N && (FromN + sizeof...(ArgsT)) == N) > >
constexpr Array(const Array< T, FromN >& a, ArgsT&&... args) noexcept
: std::array< T, N >(TuppleToArray(
std::tuple_cat(ArrayToTupple(a), ArgsToTuple(std::forward< ArgsT >(args)...))))

constexpr Array(const Array& a) noexcept = default;

constexpr Array(Array&& a) noexcept = default;

template< typename U >
constexpr explicit Array(const Array< U, N >& a) noexcept
: std::array< T, N >(StaticCastArray< T >(a))

~Array() = default;

constexpr Array& operator=(const Array& a) noexcept = default;

constexpr Array& operator=(Array&& a) noexcept = default;

// It would be nice to have properties in C++ (supported in msvc++ and Clang).

constexpr std::enable_if_t< ( 1 <= N ), T& > GetX() noexcept
return std::array< T, N >::operator(0);

constexpr std::enable_if_t< ( 2 <= N ), T& > GetY() noexcept
return std::array< T, N >::operator(1);

constexpr std::enable_if_t< ( 3 <= N ), T& > GetZ() noexcept
return std::array< T, N >::operator(2);

constexpr std::enable_if_t< ( 4 <= N ), T& > GetW() noexcept
return std::array< T, N >::operator(3);


constexpr std::enable_if_t< ( 1 <= N ), const T& > GetX() const noexcept
return std::array< T, N >::operator(0);

constexpr std::enable_if_t< ( 2 <= N ), const T& > GetY() const noexcept
return std::array< T, N >::operator(1);

constexpr std::enable_if_t< ( 3 <= N ), const T& > GetZ() const noexcept
return std::array< T, N >::operator(2);

constexpr std::enable_if_t< ( 4 <= N ), const T& > GetW() const noexcept
return std::array< T, N >::operator(3);

;


Some extra utilities for illustration purposes:



template< typename T, std::size_t N >
std::ostream& operator<<(std::ostream& os, const std::array< T, N >& a)
for (auto i : a) os << i << ' ';
return os << 'n';


int main()
constexpr Array< float, 5 > a;
std::cout << a;

constexpr Array< float, 5 > b( 1.5f, 2.5f, 3.5f, 4.5f, 5.5f );
std::cout << b;

constexpr Array< float, 5 > c 1.5f, 2.5f, 3.5f, 4.5f, 5.5f ;
std::cout << c;

constexpr Array< float, 6 > d(c);
std::cout << d;

constexpr Array< float, 6 > e(c, 6.5f);
std::cout << e;

constexpr Array< int, 6 > f(e);
std::cout << f;

return 0;



Edit 1:
Try It Online




  • auto instead of decltype(auto) (all methods return by value) (thanks to Incomputable)

  • Universal reference for ActionT + perfect forwarding of ActionT (thanks to Incomputable)

Edit 2:
Try It Online



  • All accessor/getter member methods are removed from Array, since they are not applicable to all possible derived classes of Array. A RGB color spectrum does not have X,Y,Z components, whereas a XYZ color spectrum does. A UVW normalized 3D texture position has its W component at index 2, whereas a 4D homogeneous position has its W component at index 3. Derived classes can implement these accessor/getter member methods themselves or inherit from pure abstract classes (as mentioned and illustrated by Incomputable). Furthermore, it is possible to add a size_t Index template argument to specify the right index. Depending on the size of T, one can also return by value for small values, and by const reference for large values. Depending on the type of T, one can use different call conventions as well (e.g. __vectorcall, __fastcall, etc.).


  • ArgsToTupple is removed, since it is just a wrapper around std::make_tupple.

  • An extra alignment template argument A is added to Array. Furthermore, some extra explicit constructors are added to support converting between Array instances with a different alignment.

Edit 3:
Try It Online



  • Added a constructor for replicating a single value. This makes sense in the absence of Arrays of at most one element. I personally see no use case for a std::array< T, 0 > or std::array< T, 1 > either, given that the number of elements needs to be known at compile time.

  • Added a std::is_convertible_v type trait to construct an Array from a given Array containing elements of a different type. This enables the construction of an Array< Array< T, N1 >, N2 > by replicating a single Array< T, N1 > using the newly added constructor.








share|improve this question












share|improve this question




share|improve this question








edited Jul 5 at 11:12
























asked Apr 30 at 18:17









Matthias

1727




1727







  • 1




    Great. Now make a github repo and share it with the world.
    – FreezePhoenix
    Apr 30 at 18:30










  • @Pheo: Github: just the sample | Github: larger project using the sample ;)
    – Matthias
    Apr 30 at 18:46











  • Um... Dang. That's a big project.
    – FreezePhoenix
    Apr 30 at 18:52






  • 1




    @Matthias, I recommend waiting a little bit before accepting an answer. People feel discouraged to post their own if one is already accepted. One or two days is usually a good time period.
    – Incomputable
    Apr 30 at 19:39










  • @Incomputable Ok, I will postpone accepting an answer.
    – Matthias
    Apr 30 at 19:41












  • 1




    Great. Now make a github repo and share it with the world.
    – FreezePhoenix
    Apr 30 at 18:30










  • @Pheo: Github: just the sample | Github: larger project using the sample ;)
    – Matthias
    Apr 30 at 18:46











  • Um... Dang. That's a big project.
    – FreezePhoenix
    Apr 30 at 18:52






  • 1




    @Matthias, I recommend waiting a little bit before accepting an answer. People feel discouraged to post their own if one is already accepted. One or two days is usually a good time period.
    – Incomputable
    Apr 30 at 19:39










  • @Incomputable Ok, I will postpone accepting an answer.
    – Matthias
    Apr 30 at 19:41







1




1




Great. Now make a github repo and share it with the world.
– FreezePhoenix
Apr 30 at 18:30




Great. Now make a github repo and share it with the world.
– FreezePhoenix
Apr 30 at 18:30












@Pheo: Github: just the sample | Github: larger project using the sample ;)
– Matthias
Apr 30 at 18:46





@Pheo: Github: just the sample | Github: larger project using the sample ;)
– Matthias
Apr 30 at 18:46













Um... Dang. That's a big project.
– FreezePhoenix
Apr 30 at 18:52




Um... Dang. That's a big project.
– FreezePhoenix
Apr 30 at 18:52




1




1




@Matthias, I recommend waiting a little bit before accepting an answer. People feel discouraged to post their own if one is already accepted. One or two days is usually a good time period.
– Incomputable
Apr 30 at 19:39




@Matthias, I recommend waiting a little bit before accepting an answer. People feel discouraged to post their own if one is already accepted. One or two days is usually a good time period.
– Incomputable
Apr 30 at 19:39












@Incomputable Ok, I will postpone accepting an answer.
– Matthias
Apr 30 at 19:41




@Incomputable Ok, I will postpone accepting an answer.
– Matthias
Apr 30 at 19:41










2 Answers
2






active

oldest

votes

















up vote
5
down vote



accepted










Encouraging ineffective/wrong usage



May be for your usage case it is important, but casts are usually an indication that something could be improved or fixed. static_casts are usually implicit. dynamic_casts are somewhat arguable, but there should be some better solution, albeit harder to find. const_casts are outright wrong (there is a case when non-const member function calls const version and then const_casts the constness away). reinterpret_casts are usually done in a more explicit manner. Having them hidden somewhere is asking for trouble.



decltype(auto)



I argued with @Quuxplusone about the feature. I argued that it is safe to use in general. I was wrong. The feature requires a great deal of care to be used correctly. This case is borderline dangerous. Never use decltype(auto) where value needs to be returned. Don't use it unless reference is expected and the referenceness needs to be preserved.



Accept by forwarding reference



Some people also call it generalized reference. It is particularly applicable to ActionT. IIRC the new wording says that the temporaries do not need to fully "materialize" if they will not outlive the scope. Transform function could take input array by forwarding reference too, as not all actions might operate on const array elements.



Inheriting constructors



It would be better to use "using declarations" (thanks to @BenSteffan) to inherit constructors and duplicate them as part of Array.



Curiously recurring templates



In my opinion, rather than adding every possible utility member function, one should write x_getter<T>, which SFINAE's correctly based on T. That will reduce the interface bloat greatly and allows people to choose.



Here is an example of x_getter I mentioned in the comments on a stub class:



#include <array>
#include <iostream>

template <typename T>
struct x_getter

friend T;

double x() const

return (*static_cast<const T*>(this))[0];


void x(double new_x)

(*static_cast<T*>(this))[0] = new_x;


private:
x_getter() = default; //prevents accidental creation
;

struct coordinates : std::array<double, 3>, x_getter<coordinates>

using std::array<double, 3>::array;
;


int main()

coordinates origin;
std::cout << origin.x();
origin.x(12.7);
std::cout << ' ' << origin.x() << 'n';



Demo.



It actually took me a trip to stackoverflow to get it to work, but now I understand why it works. static_cast is the right cast, because it will cast downwards the inheritance tree, and the only child that can hold the getter is T itself, thus no dynamic_cast is needed. It would be great though to smoothen out the ugly casts. I believe it also has a disadvantage in aggregates, but I believe it is not applicable in this case.



Now you can write several of these facades, and let users choose which ones they want. This will make mixing and matching easier for users. Also, [[no_unique_address]] in C++20 will make it as efficient as hand written code (currently they take up a little bit of space inside of the object, in C++20 empty base optimization will hopefully be performed using the tags).



Naming convention



The convention is rather unusual, and more C# style. Also, names could follow more standard library style to be grasped immediately. For example, TransformArray could be renamed to transform_construct, and be placed into utils namespace.



Tuple utilities



std::array is considered to be tuple by standard library. All of the helpers for std::tuple work the same way for std::array in terms of template metaprogramming. Thus the utilities are not too useful.






share|improve this answer























  • "expression (statement?)" -> Actually neither, this is a using-declaration (please excuse the pedantic nit-picking).
    – Ben Steffan
    Apr 30 at 19:26










  • @BenSteffan, thanks, and you're welcome to nit-pick.
    – Incomputable
    Apr 30 at 19:27






  • 1




    @Matthias, as I mentioned, when you want to return a value, don't use decltype(auto). auto will do quite well. decltype(auto) is useful in cases where you want to preserve referenceness, otherwise I would stay away from it, to not return a reference to temporary accidentally (compiler will probably emit a warning, but still it shouldn't be allowed).
    – Incomputable
    Apr 30 at 19:31






  • 1




    @Matthias, 4) I just thought that you got tired of duplicating all of the member functions. 5) I'll update the post with example, but it will take time 6) That is why I usually place naming conventions downwards, as long as they're consistently applied it is ok 7) I meant that std::tuple_element, std::tuple_size work the same way on tuples and arrays, and everything else is handled by your other functions.
    – Incomputable
    Apr 30 at 19:42






  • 1




    @Matthias, you’re right about fourth point. Only constructor inheritance is useful. I’ll update the post tomorrow. I’m going to sleep, but I’ll answer your questions tomorrow, so you can pile them up :)
    – Incomputable
    Apr 30 at 20:23

















up vote
1
down vote













Would it be better to have clearly labeled make_array functions for each kind of construction?



auto a1 = make_repeated<int,15>(42);
auto a2 = array_from_tuple (t);


Can we count on the compiler to elide the prvalue and construct the declared variable in-place without any copying? If so, you can effectively write named constructors.






share|improve this answer





















  • My actual use case is to let multiple classes inherit from Array. This is a possible alternative at the cost of always calling the right labeled functions in all child class constructors. These child class constructors, however, cannot be replaced (1) since I rely on explicit/implicit constructors depending on the conversions I want to support and (2) since I still want initializations via a constructor (e.g. RGB rgb(1.0f, 0.0f, 1.0f);). Unfortunately, I use the base Array as well for which I still like the same kind of initializations via a constructor.
    – Matthias
    May 1 at 13:50











  • The labeled functions, however, are more expressive, since at the moment I am not able to integrate the replication of a single value (confusing in my current setup).
    – Matthias
    May 1 at 13:52










  • Replicate single value: hint: index_sequence, comma operator, pack expansion in initializer list.
    – JDługosz
    May 1 at 18:38










  • I already have that method (see array utilities above). It just seems confusing to add it in a non-labeled constructor. But at a second thought, I can make it an explicit constructor.
    – Matthias
    May 2 at 6:14










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193285%2fc-stdarray-wrapper%23new-answer', 'question_page');

);

Post as a guest






























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
5
down vote



accepted










Encouraging ineffective/wrong usage



May be for your usage case it is important, but casts are usually an indication that something could be improved or fixed. static_casts are usually implicit. dynamic_casts are somewhat arguable, but there should be some better solution, albeit harder to find. const_casts are outright wrong (there is a case when non-const member function calls const version and then const_casts the constness away). reinterpret_casts are usually done in a more explicit manner. Having them hidden somewhere is asking for trouble.



decltype(auto)



I argued with @Quuxplusone about the feature. I argued that it is safe to use in general. I was wrong. The feature requires a great deal of care to be used correctly. This case is borderline dangerous. Never use decltype(auto) where value needs to be returned. Don't use it unless reference is expected and the referenceness needs to be preserved.



Accept by forwarding reference



Some people also call it generalized reference. It is particularly applicable to ActionT. IIRC the new wording says that the temporaries do not need to fully "materialize" if they will not outlive the scope. Transform function could take input array by forwarding reference too, as not all actions might operate on const array elements.



Inheriting constructors



It would be better to use "using declarations" (thanks to @BenSteffan) to inherit constructors and duplicate them as part of Array.



Curiously recurring templates



In my opinion, rather than adding every possible utility member function, one should write x_getter<T>, which SFINAE's correctly based on T. That will reduce the interface bloat greatly and allows people to choose.



Here is an example of x_getter I mentioned in the comments on a stub class:



#include <array>
#include <iostream>

template <typename T>
struct x_getter

friend T;

double x() const

return (*static_cast<const T*>(this))[0];


void x(double new_x)

(*static_cast<T*>(this))[0] = new_x;


private:
x_getter() = default; //prevents accidental creation
;

struct coordinates : std::array<double, 3>, x_getter<coordinates>

using std::array<double, 3>::array;
;


int main()

coordinates origin;
std::cout << origin.x();
origin.x(12.7);
std::cout << ' ' << origin.x() << 'n';



Demo.



It actually took me a trip to stackoverflow to get it to work, but now I understand why it works. static_cast is the right cast, because it will cast downwards the inheritance tree, and the only child that can hold the getter is T itself, thus no dynamic_cast is needed. It would be great though to smoothen out the ugly casts. I believe it also has a disadvantage in aggregates, but I believe it is not applicable in this case.



Now you can write several of these facades, and let users choose which ones they want. This will make mixing and matching easier for users. Also, [[no_unique_address]] in C++20 will make it as efficient as hand written code (currently they take up a little bit of space inside of the object, in C++20 empty base optimization will hopefully be performed using the tags).



Naming convention



The convention is rather unusual, and more C# style. Also, names could follow more standard library style to be grasped immediately. For example, TransformArray could be renamed to transform_construct, and be placed into utils namespace.



Tuple utilities



std::array is considered to be tuple by standard library. All of the helpers for std::tuple work the same way for std::array in terms of template metaprogramming. Thus the utilities are not too useful.






share|improve this answer























  • "expression (statement?)" -> Actually neither, this is a using-declaration (please excuse the pedantic nit-picking).
    – Ben Steffan
    Apr 30 at 19:26










  • @BenSteffan, thanks, and you're welcome to nit-pick.
    – Incomputable
    Apr 30 at 19:27






  • 1




    @Matthias, as I mentioned, when you want to return a value, don't use decltype(auto). auto will do quite well. decltype(auto) is useful in cases where you want to preserve referenceness, otherwise I would stay away from it, to not return a reference to temporary accidentally (compiler will probably emit a warning, but still it shouldn't be allowed).
    – Incomputable
    Apr 30 at 19:31






  • 1




    @Matthias, 4) I just thought that you got tired of duplicating all of the member functions. 5) I'll update the post with example, but it will take time 6) That is why I usually place naming conventions downwards, as long as they're consistently applied it is ok 7) I meant that std::tuple_element, std::tuple_size work the same way on tuples and arrays, and everything else is handled by your other functions.
    – Incomputable
    Apr 30 at 19:42






  • 1




    @Matthias, you’re right about fourth point. Only constructor inheritance is useful. I’ll update the post tomorrow. I’m going to sleep, but I’ll answer your questions tomorrow, so you can pile them up :)
    – Incomputable
    Apr 30 at 20:23














up vote
5
down vote



accepted










Encouraging ineffective/wrong usage



May be for your usage case it is important, but casts are usually an indication that something could be improved or fixed. static_casts are usually implicit. dynamic_casts are somewhat arguable, but there should be some better solution, albeit harder to find. const_casts are outright wrong (there is a case when non-const member function calls const version and then const_casts the constness away). reinterpret_casts are usually done in a more explicit manner. Having them hidden somewhere is asking for trouble.



decltype(auto)



I argued with @Quuxplusone about the feature. I argued that it is safe to use in general. I was wrong. The feature requires a great deal of care to be used correctly. This case is borderline dangerous. Never use decltype(auto) where value needs to be returned. Don't use it unless reference is expected and the referenceness needs to be preserved.



Accept by forwarding reference



Some people also call it generalized reference. It is particularly applicable to ActionT. IIRC the new wording says that the temporaries do not need to fully "materialize" if they will not outlive the scope. Transform function could take input array by forwarding reference too, as not all actions might operate on const array elements.



Inheriting constructors



It would be better to use "using declarations" (thanks to @BenSteffan) to inherit constructors and duplicate them as part of Array.



Curiously recurring templates



In my opinion, rather than adding every possible utility member function, one should write x_getter<T>, which SFINAE's correctly based on T. That will reduce the interface bloat greatly and allows people to choose.



Here is an example of x_getter I mentioned in the comments on a stub class:



#include <array>
#include <iostream>

template <typename T>
struct x_getter

friend T;

double x() const

return (*static_cast<const T*>(this))[0];


void x(double new_x)

(*static_cast<T*>(this))[0] = new_x;


private:
x_getter() = default; //prevents accidental creation
;

struct coordinates : std::array<double, 3>, x_getter<coordinates>

using std::array<double, 3>::array;
;


int main()

coordinates origin;
std::cout << origin.x();
origin.x(12.7);
std::cout << ' ' << origin.x() << 'n';



Demo.



It actually took me a trip to stackoverflow to get it to work, but now I understand why it works. static_cast is the right cast, because it will cast downwards the inheritance tree, and the only child that can hold the getter is T itself, thus no dynamic_cast is needed. It would be great though to smoothen out the ugly casts. I believe it also has a disadvantage in aggregates, but I believe it is not applicable in this case.



Now you can write several of these facades, and let users choose which ones they want. This will make mixing and matching easier for users. Also, [[no_unique_address]] in C++20 will make it as efficient as hand written code (currently they take up a little bit of space inside of the object, in C++20 empty base optimization will hopefully be performed using the tags).



Naming convention



The convention is rather unusual, and more C# style. Also, names could follow more standard library style to be grasped immediately. For example, TransformArray could be renamed to transform_construct, and be placed into utils namespace.



Tuple utilities



std::array is considered to be tuple by standard library. All of the helpers for std::tuple work the same way for std::array in terms of template metaprogramming. Thus the utilities are not too useful.






share|improve this answer























  • "expression (statement?)" -> Actually neither, this is a using-declaration (please excuse the pedantic nit-picking).
    – Ben Steffan
    Apr 30 at 19:26










  • @BenSteffan, thanks, and you're welcome to nit-pick.
    – Incomputable
    Apr 30 at 19:27






  • 1




    @Matthias, as I mentioned, when you want to return a value, don't use decltype(auto). auto will do quite well. decltype(auto) is useful in cases where you want to preserve referenceness, otherwise I would stay away from it, to not return a reference to temporary accidentally (compiler will probably emit a warning, but still it shouldn't be allowed).
    – Incomputable
    Apr 30 at 19:31






  • 1




    @Matthias, 4) I just thought that you got tired of duplicating all of the member functions. 5) I'll update the post with example, but it will take time 6) That is why I usually place naming conventions downwards, as long as they're consistently applied it is ok 7) I meant that std::tuple_element, std::tuple_size work the same way on tuples and arrays, and everything else is handled by your other functions.
    – Incomputable
    Apr 30 at 19:42






  • 1




    @Matthias, you’re right about fourth point. Only constructor inheritance is useful. I’ll update the post tomorrow. I’m going to sleep, but I’ll answer your questions tomorrow, so you can pile them up :)
    – Incomputable
    Apr 30 at 20:23












up vote
5
down vote



accepted







up vote
5
down vote



accepted






Encouraging ineffective/wrong usage



May be for your usage case it is important, but casts are usually an indication that something could be improved or fixed. static_casts are usually implicit. dynamic_casts are somewhat arguable, but there should be some better solution, albeit harder to find. const_casts are outright wrong (there is a case when non-const member function calls const version and then const_casts the constness away). reinterpret_casts are usually done in a more explicit manner. Having them hidden somewhere is asking for trouble.



decltype(auto)



I argued with @Quuxplusone about the feature. I argued that it is safe to use in general. I was wrong. The feature requires a great deal of care to be used correctly. This case is borderline dangerous. Never use decltype(auto) where value needs to be returned. Don't use it unless reference is expected and the referenceness needs to be preserved.



Accept by forwarding reference



Some people also call it generalized reference. It is particularly applicable to ActionT. IIRC the new wording says that the temporaries do not need to fully "materialize" if they will not outlive the scope. Transform function could take input array by forwarding reference too, as not all actions might operate on const array elements.



Inheriting constructors



It would be better to use "using declarations" (thanks to @BenSteffan) to inherit constructors and duplicate them as part of Array.



Curiously recurring templates



In my opinion, rather than adding every possible utility member function, one should write x_getter<T>, which SFINAE's correctly based on T. That will reduce the interface bloat greatly and allows people to choose.



Here is an example of x_getter I mentioned in the comments on a stub class:



#include <array>
#include <iostream>

template <typename T>
struct x_getter

friend T;

double x() const

return (*static_cast<const T*>(this))[0];


void x(double new_x)

(*static_cast<T*>(this))[0] = new_x;


private:
x_getter() = default; //prevents accidental creation
;

struct coordinates : std::array<double, 3>, x_getter<coordinates>

using std::array<double, 3>::array;
;


int main()

coordinates origin;
std::cout << origin.x();
origin.x(12.7);
std::cout << ' ' << origin.x() << 'n';



Demo.



It actually took me a trip to stackoverflow to get it to work, but now I understand why it works. static_cast is the right cast, because it will cast downwards the inheritance tree, and the only child that can hold the getter is T itself, thus no dynamic_cast is needed. It would be great though to smoothen out the ugly casts. I believe it also has a disadvantage in aggregates, but I believe it is not applicable in this case.



Now you can write several of these facades, and let users choose which ones they want. This will make mixing and matching easier for users. Also, [[no_unique_address]] in C++20 will make it as efficient as hand written code (currently they take up a little bit of space inside of the object, in C++20 empty base optimization will hopefully be performed using the tags).



Naming convention



The convention is rather unusual, and more C# style. Also, names could follow more standard library style to be grasped immediately. For example, TransformArray could be renamed to transform_construct, and be placed into utils namespace.



Tuple utilities



std::array is considered to be tuple by standard library. All of the helpers for std::tuple work the same way for std::array in terms of template metaprogramming. Thus the utilities are not too useful.






share|improve this answer















Encouraging ineffective/wrong usage



May be for your usage case it is important, but casts are usually an indication that something could be improved or fixed. static_casts are usually implicit. dynamic_casts are somewhat arguable, but there should be some better solution, albeit harder to find. const_casts are outright wrong (there is a case when non-const member function calls const version and then const_casts the constness away). reinterpret_casts are usually done in a more explicit manner. Having them hidden somewhere is asking for trouble.



decltype(auto)



I argued with @Quuxplusone about the feature. I argued that it is safe to use in general. I was wrong. The feature requires a great deal of care to be used correctly. This case is borderline dangerous. Never use decltype(auto) where value needs to be returned. Don't use it unless reference is expected and the referenceness needs to be preserved.



Accept by forwarding reference



Some people also call it generalized reference. It is particularly applicable to ActionT. IIRC the new wording says that the temporaries do not need to fully "materialize" if they will not outlive the scope. Transform function could take input array by forwarding reference too, as not all actions might operate on const array elements.



Inheriting constructors



It would be better to use "using declarations" (thanks to @BenSteffan) to inherit constructors and duplicate them as part of Array.



Curiously recurring templates



In my opinion, rather than adding every possible utility member function, one should write x_getter<T>, which SFINAE's correctly based on T. That will reduce the interface bloat greatly and allows people to choose.



Here is an example of x_getter I mentioned in the comments on a stub class:



#include <array>
#include <iostream>

template <typename T>
struct x_getter

friend T;

double x() const

return (*static_cast<const T*>(this))[0];


void x(double new_x)

(*static_cast<T*>(this))[0] = new_x;


private:
x_getter() = default; //prevents accidental creation
;

struct coordinates : std::array<double, 3>, x_getter<coordinates>

using std::array<double, 3>::array;
;


int main()

coordinates origin;
std::cout << origin.x();
origin.x(12.7);
std::cout << ' ' << origin.x() << 'n';



Demo.



It actually took me a trip to stackoverflow to get it to work, but now I understand why it works. static_cast is the right cast, because it will cast downwards the inheritance tree, and the only child that can hold the getter is T itself, thus no dynamic_cast is needed. It would be great though to smoothen out the ugly casts. I believe it also has a disadvantage in aggregates, but I believe it is not applicable in this case.



Now you can write several of these facades, and let users choose which ones they want. This will make mixing and matching easier for users. Also, [[no_unique_address]] in C++20 will make it as efficient as hand written code (currently they take up a little bit of space inside of the object, in C++20 empty base optimization will hopefully be performed using the tags).



Naming convention



The convention is rather unusual, and more C# style. Also, names could follow more standard library style to be grasped immediately. For example, TransformArray could be renamed to transform_construct, and be placed into utils namespace.



Tuple utilities



std::array is considered to be tuple by standard library. All of the helpers for std::tuple work the same way for std::array in terms of template metaprogramming. Thus the utilities are not too useful.







share|improve this answer















share|improve this answer



share|improve this answer








edited May 1 at 19:12


























answered Apr 30 at 19:12









Incomputable

5,99721144




5,99721144











  • "expression (statement?)" -> Actually neither, this is a using-declaration (please excuse the pedantic nit-picking).
    – Ben Steffan
    Apr 30 at 19:26










  • @BenSteffan, thanks, and you're welcome to nit-pick.
    – Incomputable
    Apr 30 at 19:27






  • 1




    @Matthias, as I mentioned, when you want to return a value, don't use decltype(auto). auto will do quite well. decltype(auto) is useful in cases where you want to preserve referenceness, otherwise I would stay away from it, to not return a reference to temporary accidentally (compiler will probably emit a warning, but still it shouldn't be allowed).
    – Incomputable
    Apr 30 at 19:31






  • 1




    @Matthias, 4) I just thought that you got tired of duplicating all of the member functions. 5) I'll update the post with example, but it will take time 6) That is why I usually place naming conventions downwards, as long as they're consistently applied it is ok 7) I meant that std::tuple_element, std::tuple_size work the same way on tuples and arrays, and everything else is handled by your other functions.
    – Incomputable
    Apr 30 at 19:42






  • 1




    @Matthias, you’re right about fourth point. Only constructor inheritance is useful. I’ll update the post tomorrow. I’m going to sleep, but I’ll answer your questions tomorrow, so you can pile them up :)
    – Incomputable
    Apr 30 at 20:23
















  • "expression (statement?)" -> Actually neither, this is a using-declaration (please excuse the pedantic nit-picking).
    – Ben Steffan
    Apr 30 at 19:26










  • @BenSteffan, thanks, and you're welcome to nit-pick.
    – Incomputable
    Apr 30 at 19:27






  • 1




    @Matthias, as I mentioned, when you want to return a value, don't use decltype(auto). auto will do quite well. decltype(auto) is useful in cases where you want to preserve referenceness, otherwise I would stay away from it, to not return a reference to temporary accidentally (compiler will probably emit a warning, but still it shouldn't be allowed).
    – Incomputable
    Apr 30 at 19:31






  • 1




    @Matthias, 4) I just thought that you got tired of duplicating all of the member functions. 5) I'll update the post with example, but it will take time 6) That is why I usually place naming conventions downwards, as long as they're consistently applied it is ok 7) I meant that std::tuple_element, std::tuple_size work the same way on tuples and arrays, and everything else is handled by your other functions.
    – Incomputable
    Apr 30 at 19:42






  • 1




    @Matthias, you’re right about fourth point. Only constructor inheritance is useful. I’ll update the post tomorrow. I’m going to sleep, but I’ll answer your questions tomorrow, so you can pile them up :)
    – Incomputable
    Apr 30 at 20:23















"expression (statement?)" -> Actually neither, this is a using-declaration (please excuse the pedantic nit-picking).
– Ben Steffan
Apr 30 at 19:26




"expression (statement?)" -> Actually neither, this is a using-declaration (please excuse the pedantic nit-picking).
– Ben Steffan
Apr 30 at 19:26












@BenSteffan, thanks, and you're welcome to nit-pick.
– Incomputable
Apr 30 at 19:27




@BenSteffan, thanks, and you're welcome to nit-pick.
– Incomputable
Apr 30 at 19:27




1




1




@Matthias, as I mentioned, when you want to return a value, don't use decltype(auto). auto will do quite well. decltype(auto) is useful in cases where you want to preserve referenceness, otherwise I would stay away from it, to not return a reference to temporary accidentally (compiler will probably emit a warning, but still it shouldn't be allowed).
– Incomputable
Apr 30 at 19:31




@Matthias, as I mentioned, when you want to return a value, don't use decltype(auto). auto will do quite well. decltype(auto) is useful in cases where you want to preserve referenceness, otherwise I would stay away from it, to not return a reference to temporary accidentally (compiler will probably emit a warning, but still it shouldn't be allowed).
– Incomputable
Apr 30 at 19:31




1




1




@Matthias, 4) I just thought that you got tired of duplicating all of the member functions. 5) I'll update the post with example, but it will take time 6) That is why I usually place naming conventions downwards, as long as they're consistently applied it is ok 7) I meant that std::tuple_element, std::tuple_size work the same way on tuples and arrays, and everything else is handled by your other functions.
– Incomputable
Apr 30 at 19:42




@Matthias, 4) I just thought that you got tired of duplicating all of the member functions. 5) I'll update the post with example, but it will take time 6) That is why I usually place naming conventions downwards, as long as they're consistently applied it is ok 7) I meant that std::tuple_element, std::tuple_size work the same way on tuples and arrays, and everything else is handled by your other functions.
– Incomputable
Apr 30 at 19:42




1




1




@Matthias, you’re right about fourth point. Only constructor inheritance is useful. I’ll update the post tomorrow. I’m going to sleep, but I’ll answer your questions tomorrow, so you can pile them up :)
– Incomputable
Apr 30 at 20:23




@Matthias, you’re right about fourth point. Only constructor inheritance is useful. I’ll update the post tomorrow. I’m going to sleep, but I’ll answer your questions tomorrow, so you can pile them up :)
– Incomputable
Apr 30 at 20:23












up vote
1
down vote













Would it be better to have clearly labeled make_array functions for each kind of construction?



auto a1 = make_repeated<int,15>(42);
auto a2 = array_from_tuple (t);


Can we count on the compiler to elide the prvalue and construct the declared variable in-place without any copying? If so, you can effectively write named constructors.






share|improve this answer





















  • My actual use case is to let multiple classes inherit from Array. This is a possible alternative at the cost of always calling the right labeled functions in all child class constructors. These child class constructors, however, cannot be replaced (1) since I rely on explicit/implicit constructors depending on the conversions I want to support and (2) since I still want initializations via a constructor (e.g. RGB rgb(1.0f, 0.0f, 1.0f);). Unfortunately, I use the base Array as well for which I still like the same kind of initializations via a constructor.
    – Matthias
    May 1 at 13:50











  • The labeled functions, however, are more expressive, since at the moment I am not able to integrate the replication of a single value (confusing in my current setup).
    – Matthias
    May 1 at 13:52










  • Replicate single value: hint: index_sequence, comma operator, pack expansion in initializer list.
    – JDługosz
    May 1 at 18:38










  • I already have that method (see array utilities above). It just seems confusing to add it in a non-labeled constructor. But at a second thought, I can make it an explicit constructor.
    – Matthias
    May 2 at 6:14














up vote
1
down vote













Would it be better to have clearly labeled make_array functions for each kind of construction?



auto a1 = make_repeated<int,15>(42);
auto a2 = array_from_tuple (t);


Can we count on the compiler to elide the prvalue and construct the declared variable in-place without any copying? If so, you can effectively write named constructors.






share|improve this answer





















  • My actual use case is to let multiple classes inherit from Array. This is a possible alternative at the cost of always calling the right labeled functions in all child class constructors. These child class constructors, however, cannot be replaced (1) since I rely on explicit/implicit constructors depending on the conversions I want to support and (2) since I still want initializations via a constructor (e.g. RGB rgb(1.0f, 0.0f, 1.0f);). Unfortunately, I use the base Array as well for which I still like the same kind of initializations via a constructor.
    – Matthias
    May 1 at 13:50











  • The labeled functions, however, are more expressive, since at the moment I am not able to integrate the replication of a single value (confusing in my current setup).
    – Matthias
    May 1 at 13:52










  • Replicate single value: hint: index_sequence, comma operator, pack expansion in initializer list.
    – JDługosz
    May 1 at 18:38










  • I already have that method (see array utilities above). It just seems confusing to add it in a non-labeled constructor. But at a second thought, I can make it an explicit constructor.
    – Matthias
    May 2 at 6:14












up vote
1
down vote










up vote
1
down vote









Would it be better to have clearly labeled make_array functions for each kind of construction?



auto a1 = make_repeated<int,15>(42);
auto a2 = array_from_tuple (t);


Can we count on the compiler to elide the prvalue and construct the declared variable in-place without any copying? If so, you can effectively write named constructors.






share|improve this answer













Would it be better to have clearly labeled make_array functions for each kind of construction?



auto a1 = make_repeated<int,15>(42);
auto a2 = array_from_tuple (t);


Can we count on the compiler to elide the prvalue and construct the declared variable in-place without any copying? If so, you can effectively write named constructors.







share|improve this answer













share|improve this answer



share|improve this answer











answered May 1 at 13:25









JDługosz

5,047731




5,047731











  • My actual use case is to let multiple classes inherit from Array. This is a possible alternative at the cost of always calling the right labeled functions in all child class constructors. These child class constructors, however, cannot be replaced (1) since I rely on explicit/implicit constructors depending on the conversions I want to support and (2) since I still want initializations via a constructor (e.g. RGB rgb(1.0f, 0.0f, 1.0f);). Unfortunately, I use the base Array as well for which I still like the same kind of initializations via a constructor.
    – Matthias
    May 1 at 13:50











  • The labeled functions, however, are more expressive, since at the moment I am not able to integrate the replication of a single value (confusing in my current setup).
    – Matthias
    May 1 at 13:52










  • Replicate single value: hint: index_sequence, comma operator, pack expansion in initializer list.
    – JDługosz
    May 1 at 18:38










  • I already have that method (see array utilities above). It just seems confusing to add it in a non-labeled constructor. But at a second thought, I can make it an explicit constructor.
    – Matthias
    May 2 at 6:14
















  • My actual use case is to let multiple classes inherit from Array. This is a possible alternative at the cost of always calling the right labeled functions in all child class constructors. These child class constructors, however, cannot be replaced (1) since I rely on explicit/implicit constructors depending on the conversions I want to support and (2) since I still want initializations via a constructor (e.g. RGB rgb(1.0f, 0.0f, 1.0f);). Unfortunately, I use the base Array as well for which I still like the same kind of initializations via a constructor.
    – Matthias
    May 1 at 13:50











  • The labeled functions, however, are more expressive, since at the moment I am not able to integrate the replication of a single value (confusing in my current setup).
    – Matthias
    May 1 at 13:52










  • Replicate single value: hint: index_sequence, comma operator, pack expansion in initializer list.
    – JDługosz
    May 1 at 18:38










  • I already have that method (see array utilities above). It just seems confusing to add it in a non-labeled constructor. But at a second thought, I can make it an explicit constructor.
    – Matthias
    May 2 at 6:14















My actual use case is to let multiple classes inherit from Array. This is a possible alternative at the cost of always calling the right labeled functions in all child class constructors. These child class constructors, however, cannot be replaced (1) since I rely on explicit/implicit constructors depending on the conversions I want to support and (2) since I still want initializations via a constructor (e.g. RGB rgb(1.0f, 0.0f, 1.0f);). Unfortunately, I use the base Array as well for which I still like the same kind of initializations via a constructor.
– Matthias
May 1 at 13:50





My actual use case is to let multiple classes inherit from Array. This is a possible alternative at the cost of always calling the right labeled functions in all child class constructors. These child class constructors, however, cannot be replaced (1) since I rely on explicit/implicit constructors depending on the conversions I want to support and (2) since I still want initializations via a constructor (e.g. RGB rgb(1.0f, 0.0f, 1.0f);). Unfortunately, I use the base Array as well for which I still like the same kind of initializations via a constructor.
– Matthias
May 1 at 13:50













The labeled functions, however, are more expressive, since at the moment I am not able to integrate the replication of a single value (confusing in my current setup).
– Matthias
May 1 at 13:52




The labeled functions, however, are more expressive, since at the moment I am not able to integrate the replication of a single value (confusing in my current setup).
– Matthias
May 1 at 13:52












Replicate single value: hint: index_sequence, comma operator, pack expansion in initializer list.
– JDługosz
May 1 at 18:38




Replicate single value: hint: index_sequence, comma operator, pack expansion in initializer list.
– JDługosz
May 1 at 18:38












I already have that method (see array utilities above). It just seems confusing to add it in a non-labeled constructor. But at a second thought, I can make it an explicit constructor.
– Matthias
May 2 at 6:14




I already have that method (see array utilities above). It just seems confusing to add it in a non-labeled constructor. But at a second thought, I can make it an explicit constructor.
– Matthias
May 2 at 6:14












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193285%2fc-stdarray-wrapper%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Chat program with C++ and SFML

Function to Return a JSON Like Objects Using VBA Collections and Arrays

Will my employers contract hold up in court?