I am currently working on a small mathematical vector class.
I want two vectors class, Vector2
and Vector3
to be constructible from one to another.
For example:
Vector2<float> vec2(18.5f, 32.1f); // x = 18.5; y = 32.1
Vector3<float> vec3(vec2); // x = 18.5; y = 32.1; z = float()
To do so, and to ease extensibility, I would like to use a traits VectorTraits
with its basic definition as so:
template <typename T>
struct VectorTraits
{
typedef T VectorType;
typedef typename T::ValueType ValueType;
static const unsigned int dimension = T::dimension;
};
This form would allow an user to make a link between existing Vectors classes (like glm::vec2, for example) and my classes. It would then be possible to create a Vector2 from a glm::vec2.
Moreover, this technique could allow me to write a generic streaming operator for all classes defining a VectorTraits
using SFINAE.
There is my problem, I haven't been able to define the operator<<
so that is silently errors when VectorTraits
is inapropriate for the given type.
Here is my last attempt (Ideone link here):
#include <iostream>
#include <type_traits>
// To define another operator
struct Dummy
{};
// Traits class
template <typename T>
struct VectorTraits
{
typedef T VectorType;
typedef typename T::ValueType ValueType;
static const std::uint16_t dimension = T::dimension;
};
// Fake vector class. Defines the required typedef.
struct Vec
{
typedef float ValueType;
static const std::uint16_t dimension = 2;
};
// Streaming operator for Dummy.
std::ostream& operator<<(std::ostream& stream, const Dummy& d)
{
stream << "dummy.\n";
return stream;
}
// Streaming operator attempt for classes defining VectorTraits.
template <class T, std::enable_if_t<(VectorTraits<T>::dimension > 0)>>
std::ostream& operator<<(std::ostream& stream, const T& vec)
{
std::cout << "Traits. Dimension = " << VectorTraits<T>::dimension << "\n";
}
int main()
{
std::cout << "Test\n";
std::cout << Vec();
std::cout << Dummy();
return 0;
}
With this attempt, the error simply is
error: no match for 'operator<<' (operand types are 'std::ostream {aka std::basic_ostream<char>}' and 'Vec')
prog.cpp:33:15: note: candidate: template<class T, typename std::enable_if<(VectorTraits<T>::dimension > 0), void>::type <anonymous> > std::ostream& operator<<(std::ostream&, const T&)
std::ostream& operator<<(std::ostream& stream, const T& vec)
^
prog.cpp:33:15: note: template argument deduction/substitution failed:
prog.cpp:41:19: note: couldn't deduce template parameter '<anonymous>'
If I change
template <class T, std::enable_if_t<(VectorTraits<T>::dimension > 0)>>
to
template <class T, std::enable_if_t<(VectorTraits<T>::dimension > 0)>* = 0>
I get another error
prog.cpp:13:35: error: 'char [21]' is not a class, struct, or union type
typedef typename T::ValueType ValueType;
The only version I managed to get working was with an empty VectorTraits
class which had to be specialized for each Vector
. But I also wanted to provide a way to "automatically" be a Vector
with some typedef
s defined.
I don't understand why, in the show version, my operator doesn't get retained by the compiler. I also tried some variants but it always either matches everything or nothing.
One issue is that you don't supply a default argument to the result of your std::enable_if_t
instantiation, so template argument deduction fails. One way to fix this is to add * = nullptr
to it:
template <class T, std::enable_if_t<(VectorTraits<T>::dimension > 0)>* = nullptr>
std::ostream& operator<<(std::ostream& stream, const T& vec)
However, now we get an error because inside the VectorTraits<T>
instantiation, T::ValueType
is required. This is not in an SFINAE context, so a hard fail will occur if that member does not exist. We can fix this by adding an SFINAE check to it in our template parameters:
template <class T, typename = typename T::ValueType,
std::enable_if_t<(VectorTraits<T>::dimension > 0)>* = nullptr>
std::ostream& operator<<(std::ostream& stream, const T& vec)
You could factor this out into an external IsValidVector
check so that you have a single point to update if you require these kinds of checks a number of times.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With