Mayank Shigaonker
Mayank Shigaonker

Reputation: 63

How to create a class specialization that does different things for floats and ints?

I want to create a struct called Vec2 which looks something like this:

template<typename T>
struct Vec2 {
  T x, y;

  // Other things are also there like Constructors, Destructors, operator overloads, etc.
};

I want to be able to create an operator overload for the % operator. The overload looks something like this

Vec2<T> operator%(const Vec2<T> &other) {
  return Vec2<T>(this-> x % other.x, this.y % other.y);
}

Now the problem here is that if someone instantiates this struct with a template parameter of float then this operator overload breaks as you can't modulo floats with the default % operator in C++. So in that case I wanted to use this function instead.

Vec2<T> operator%(const Floating &other) {
  return Vec2<T>(fmod(this->x, other.x), fmod(this->y, other.y));
}

So what can I do so that if someone instantiates this struct with with float as a template param then they should have access to the second operator overload otherwise the first.

I've managed to create a template that helps me filter out all float template parameters like this

template<typename T,
         typename  = std::enable_if_t<std::is_floating_point_v<Integral>>>
struct Vec2 {
  T x, y;

  // Other things are also there like Constructors, Destructors, operator overloads, etc.
  Vec2<T> operator%(const Vec2<T> &other) {
    return Vec2<T>(fmod(this->x, other.x), fmod(this->y, other.y));
  }
};

This makes it so that no one can instantiate this class with an Integral value. But I don't want this to happen. I want them to be able to instantiate this class also with an Integral value, just with a different member function. How can achieve this? I've tried doing some partial specialization but it didn't work out.

Note: I have a slightly different structure in my actual code with a base class containing all common code but I didn't include it over here for simplicity.

I'm using GCC 11.1.0. The C++ standard doesn't really matter as I can even use C++20.

Upvotes: 0

Views: 46

Answers (1)

Lukas-T
Lukas-T

Reputation: 11350

In this case a simple if constexpr should be enough:

Vec2<T> operator%(const Vec2<T> &other) 
{
    if constexpr (std::is_floating_point_v<T>)
    {
        return Vec2<T>(fmod(this->x, other.x), fmod(this->y, other.y));
    }
    else 
    {
        return Vec2<T>(this-> x % other.x, this->y % other.y);
    }
}

Here I used std::is_floating_point_v to determine at compile time wether T is any floating point type or not. Only one of the two branches will be present in the respective template instantiation.

I think that's far simpler than specializing the whole template for float/non-float and in my opinion more readable than SFINAE.

Upvotes: 1

Related Questions