Primer: C++ base class constructor taking derived class as argument (?)
I have a Vector
and a Vector2D
class. The former should contain a constructor that allows element-wise type casting. It should be allowed for the derived class too. For some cases, it already works, (see examples below) but i think some SFINAE magic is what's missing.
Vector
#include <array>
#include <type_traits>
namespace mu {
template<std::size_t N, typename T>
class Vector {
public:
// ...
template <typename... TArgs,
std::enable_if_t<sizeof...(TArgs) == N ||
(!std::is_base_of_v<Vector, TArgs> && ...), int> = 0>
Vector(TArgs... args) : data({args...}) {}
// this should always be called for type casting:
template <std::size_t Nn, typename Tt>
Vector(const Vector<Nn, Tt> &other) {
static_assert(N == Nn, "Vector size mismatch");
for (std::size_t i = 0; i < N; i++) {
data[i] = static_cast<T>(other[i]);
}
}
Vector(const Vector &other) = default; // copy constructor
protected:
std::array<T, N> data;
};
}
Vector2D
namespace mu {
template<typename T>
class Vector2D : public Vector<2,T> {
public:
using Vector<2, T>::Vector; // inherit base class constructors
Vector2D(const Vector<2, T>& other) : Vector<2, T>(other) {}
// Vector2D specific functions, e.g. rotation
//...
};
}
Examples (they should all compile)
// Example 1 (compiles)
mu::Vector<2, int> a{1, 2};
mu::Vector<2, float> b{a};
// Example 2 (compiles)
mu::Vector<2, int> c{1, 2};
mu::Vector2D<float> d{c};
// Example 3 (doesn't compile)
mu::Vector2D<int> e{1, 2};
mu::Vector<2, float> f{e};
// Example 4 (doesn't compile)
mu::Vector2D<int> g{1, 2};
mu::Vector2D<float> h{g};
Error (examples 3 & 4)
.../vector.h:63:27: error: no matching constructor for initialization of 'std::array<float, 2UL>'
Vector(TArgs... args) : data_({args...}) {}
The code tries to call the wrong constructor instead of the one that contains the type casting. I already tried to add another constraint to the enable_if term but so far with no good result.
@Jarod42 pointed me in the right direction
What i need is &&
instead of ||
for the two arguments. However, std::is_base_of
is not really enforcing what i want. I replaced it with std::is_same
to check if all the arguments supplied by ...TArgs
are actually of the same type as the one that the Vector already holds, T
.
Now, this constructor can only be called by giving it the exact N
number of arguments that are of type T
template <typename... TArgs,
std::enable_if_t<sizeof...(TArgs) == N &&
(std::is_same_v<T, TArgs> && ...), int> = 0>
Vector(TArgs... args) : data_({args...}) {}