Enable function only for some template specification if template is of integer type

Viewed 158

I have a Vector class which has a template of <unsigned int Dim>, eg. I can do Vector<2> for a vector in 2-dimensional space, Vector<4> for 4-dimensional, etc. I want to add some methods to the class if Dim == specific value, for instance CrossProduct for Vector<3>, or x,y,z,w getters for a vector of sufficient dimension. If used with a Vector of incorrect dimensions, I would hope to get a compile error.

I have looked around quite a lot, one of the things that I believe are close enough is std::enable_if, however, I have no clue how to use it for my particular case (where the condition is either Dim == x, or Dim > x).

Is this the right way, or is there a completely different way I should do this?

I am using C++17, by the way.

3 Answers
  • pre-C++20, static_assert might be the simplest:

    template <std::size_t Dims>
    struct Vector
    {
        // ...
        double getZ() const { static_assert(Dims >= 3);  return data[2]; }
    };
    
  • SFINAE is possible, but complex and verbose:

    template <std::size_t Dims>
    struct Vector
    {
        // ...
        template <std::size_t D = Dims, std::enable_if_t<(D >= 3) && D == Dims, int> = 0>
        double getZ() const { return data[2]; }
    };
    
  • Specialization is possible, but might be tricky, for example:

    struct NullVector3{};
    
    template <typename Derived>
    struct Vector3
    {
        // ...
        double getZ() const { return static_cast<Derived*>(this)->data[2]; }
    };
    
    template <std::size_t Dims>
    struct Vector : std::conditional_t<(Dims >= 3), Vector3<Vector<Dims>>, NullVector3> /*, ..*/
    {
        // ...
        // inherit of Vector3<Vector>::getZ() when Dims >= 3
        // inherit of no extra method from NullVector3 else.
    };
    
  • C++20 is the simplest with requires:

    template <std::size_t Dims>
    struct Vector
    {
        // ...
        double getZ() const requires(Dims >= 3) { return data[2]; }
    };
    

In C++20 you may use Constraints and Concepts.

With C++17, you need to emulate them with std::enable_if. Alternatively you could use static_assert, but I would deem enable_if to express the intention better (automatic tools like code-completion should figure enable_if out, but most probably not a static assert).

A practical example is given in this answer as found by @JHBonarius.

I would implement the methods for the generic interface and add

static_assert(Dim==3,"This method is only valid for 3-dimensional vectors.")

as the first line in each method. This will yield much cleaner compiler error message compared to std::enable_if approach.

You can also use if constexpr(Dim==x) for the rest of the method's body and to "specialize" the code based on the current dimension if varying implementations are needed.

Related