I have a class that encapsulates a stl compatible container type which is the only class member and gives a lot of vector math functions that can be applied on this vector.
This class has various kinds of constructors, one of them is a constructor that takes an initialiser list:
template <class Type, class VectorType = std::vector<Type>>
class MathVector
{
public:
using initializer_list = std::initializer_list<Type>;
MathVector (initializer_list il) : vector(il) {}
// many more constructors and member functions here
private:
VectorType vector:
}
While something like MathVector<int> foo = { 1, 2, 3 }
works well, MathVector<int, std::array<int, 3>> bar = { 1, 2, 3 }
fails to compile on clang with an error like
(86, 55) No matching constructor for initialization of 'std::__1::array<int, 3>'
std::vector<int> foo = { 1, 2, 3 }
and std::array<int, 3> bar = { 1, 2, 3 }
works, so I guess that despite the same syntax, std::array
is not really constructed through an initializer list in this case. This guess gets stronger when looking into the std library sources where I don't find any initializer list based std::array
constructor. Furthermore, cppreference tells me that it can be initialized with aggregate-initialization – which does not even seem to be any kind of usual constructor. So is there a way to create a constructor for my class that correctly forwards an initialization with the desired syntax to a std::array
member?
With tag-dispatching:
template <typename T>
struct type_identity { using type = T; };
template <typename Type, typename VectorType = std::vector<Type>>
class MathVector
{
public:
MathVector(std::initializer_list<Type> il)
: MathVector(il, type_identity<VectorType>{}) {}
private:
template <typename T>
MathVector(std::initializer_list<Type> il, type_identity<T>)
: vector(il) {}
template <typename T, std::size_t N>
MathVector(std::initializer_list<Type> il, type_identity<std::array<T, N>>)
: MathVector(il, std::make_index_sequence<N>{}) {}
template <std::size_t... Is>
MathVector(std::initializer_list<Type> il, std::index_sequence<Is...>)
: vector{ *(il.begin() + Is)... } {}
VectorType vector;
};
An alternative solution is to use a variadic template constructor.
template <typename Type, typename VectorType = std::vector<Type>>
class MathVector
{
public:
template <typename... Ts>
MathVector(Ts&&... ts)
: vector{ std::forward<Ts>(ts)... } {}
MathVector(MathVector& rhs)
: MathVector(const_cast<const MathVector&>(rhs)) {}
MathVector(const MathVector& rhs)
: vector(rhs.vector) {}
private:
VectorType vector;
};
or shorter in c++20:
template <typename Type, typename VectorType = std::vector<Type>>
class MathVector
{
public:
MathVector(std::convertible_to<Type> auto&&... ts)
: vector{ std::forward<decltype(ts)>(ts)... } {}
private:
VectorType vector;
};