C++ std::array wrapper
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
8
down vote
favorite
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
Array
s 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 tostd::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 ofdecltype(auto)
(all methods return by value) (thanks to Incomputable)- Universal reference for
ActionT
+ perfect forwarding ofActionT
(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 ofArray
. 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 asize_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 aroundstd::make_tupple
.- An extra alignment template argument
A
is added toArray
. Furthermore, some extraexplicit
constructors are added to support converting betweenArray
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
Array
s of at most one element. I personally see no use case for astd::array< T, 0 >
orstd::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 anArray
from a givenArray
containing elements of a different type. This enables the construction of anArray< Array< T, N1 >, N2 >
by replicating a singleArray< T, N1 >
using the newly added constructor.
c++ c++17
add a comment |Â
up vote
8
down vote
favorite
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
Array
s 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 tostd::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 ofdecltype(auto)
(all methods return by value) (thanks to Incomputable)- Universal reference for
ActionT
+ perfect forwarding ofActionT
(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 ofArray
. 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 asize_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 aroundstd::make_tupple
.- An extra alignment template argument
A
is added toArray
. Furthermore, some extraexplicit
constructors are added to support converting betweenArray
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
Array
s of at most one element. I personally see no use case for astd::array< T, 0 >
orstd::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 anArray
from a givenArray
containing elements of a different type. This enables the construction of anArray< Array< T, N1 >, N2 >
by replicating a singleArray< T, N1 >
using the newly added constructor.
c++ c++17
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
add a comment |Â
up vote
8
down vote
favorite
up vote
8
down vote
favorite
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
Array
s 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 tostd::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 ofdecltype(auto)
(all methods return by value) (thanks to Incomputable)- Universal reference for
ActionT
+ perfect forwarding ofActionT
(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 ofArray
. 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 asize_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 aroundstd::make_tupple
.- An extra alignment template argument
A
is added toArray
. Furthermore, some extraexplicit
constructors are added to support converting betweenArray
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
Array
s of at most one element. I personally see no use case for astd::array< T, 0 >
orstd::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 anArray
from a givenArray
containing elements of a different type. This enables the construction of anArray< Array< T, N1 >, N2 >
by replicating a singleArray< T, N1 >
using the newly added constructor.
c++ c++17
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
Array
s 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 tostd::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 ofdecltype(auto)
(all methods return by value) (thanks to Incomputable)- Universal reference for
ActionT
+ perfect forwarding ofActionT
(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 ofArray
. 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 asize_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 aroundstd::make_tupple
.- An extra alignment template argument
A
is added toArray
. Furthermore, some extraexplicit
constructors are added to support converting betweenArray
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
Array
s of at most one element. I personally see no use case for astd::array< T, 0 >
orstd::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 anArray
from a givenArray
containing elements of a different type. This enables the construction of anArray< Array< T, N1 >, N2 >
by replicating a singleArray< T, N1 >
using the newly added constructor.
c++ c++17
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
add a comment |Â
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
add a comment |Â
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_cast
s are usually implicit. dynamic_cast
s are somewhat arguable, but there should be some better solution, albeit harder to find. const_cast
s are outright wrong (there is a case when non-const member function calls const version and then const_cast
s the constness away). reinterpret_cast
s 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.
"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 usedecltype(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 thatstd::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
 |Â
show 3 more comments
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.
My actual use case is to let multiple classes inherit fromArray
. 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 baseArray
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
add a comment |Â
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_cast
s are usually implicit. dynamic_cast
s are somewhat arguable, but there should be some better solution, albeit harder to find. const_cast
s are outright wrong (there is a case when non-const member function calls const version and then const_cast
s the constness away). reinterpret_cast
s 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.
"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 usedecltype(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 thatstd::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
 |Â
show 3 more comments
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_cast
s are usually implicit. dynamic_cast
s are somewhat arguable, but there should be some better solution, albeit harder to find. const_cast
s are outright wrong (there is a case when non-const member function calls const version and then const_cast
s the constness away). reinterpret_cast
s 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.
"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 usedecltype(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 thatstd::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
 |Â
show 3 more comments
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_cast
s are usually implicit. dynamic_cast
s are somewhat arguable, but there should be some better solution, albeit harder to find. const_cast
s are outright wrong (there is a case when non-const member function calls const version and then const_cast
s the constness away). reinterpret_cast
s 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.
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_cast
s are usually implicit. dynamic_cast
s are somewhat arguable, but there should be some better solution, albeit harder to find. const_cast
s are outright wrong (there is a case when non-const member function calls const version and then const_cast
s the constness away). reinterpret_cast
s 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.
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 usedecltype(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 thatstd::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
 |Â
show 3 more comments
"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 usedecltype(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 thatstd::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
 |Â
show 3 more comments
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.
My actual use case is to let multiple classes inherit fromArray
. 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 baseArray
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
add a comment |Â
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.
My actual use case is to let multiple classes inherit fromArray
. 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 baseArray
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
add a comment |Â
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.
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.
answered May 1 at 13:25
JDÃ Âugosz
5,047731
5,047731
My actual use case is to let multiple classes inherit fromArray
. 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 baseArray
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
add a comment |Â
My actual use case is to let multiple classes inherit fromArray
. 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 baseArray
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
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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