Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apply SFINAE to check if a trait is defined for T

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 typedefs 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.

like image 397
Telokis Avatar asked Jan 07 '23 09:01

Telokis


1 Answers

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.

like image 147
TartanLlama Avatar answered Jan 12 '23 00:01

TartanLlama