2

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.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
kubci98
  • 368
  • 7
  • 20
  • 1
    Would be simpler in C++20 with `requires`. – Jarod42 Mar 22 '21 at 15:02
  • What happens if you just use `std::enable_if_t` as your return type instead of `double`? Then if Dim isn't 3, the function has no valid return type, so the compiler can't generate it. – user253751 Mar 22 '21 at 15:04
  • 1
    do you want to functions to only be available for certain values of `Dim` (SFINAE). Or do you want different implementations for values of `Dim` (template specialization)? – JHBonarius Mar 22 '21 at 15:04
  • 1
    Does this answer your question? [Using C++11 std::enable\_if to enable member function if vector is specific length](https://stackoverflow.com/questions/13786479/using-c11-stdenable-if-to-enable-member-function-if-vector-is-specific-lengt) – JHBonarius Mar 22 '21 at 15:07
  • @user253751: if `Dim` is the class template, you would have hard error. `Dim` need to be function template parameter (which might be defaulted from the class' one) for substitution failure. – Jarod42 Mar 22 '21 at 15:53

3 Answers3

4
  • 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]; }
    };
    
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thanks, switched to C++20 and requires works as expected. However, I had issues with using static_assert() when compiling with MSVC++. Looked into it only a bit and it might be an MSVC++ problem though. – kubci98 Mar 22 '21 at 15:42
  • @kubci98: `static_assert` require extra C-string argument pre-C++17 -> `static_assert(Dims >= 3, "Wrong dimension");`. – Jarod42 Mar 22 '21 at 15:48
1

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.

ypnos
  • 50,202
  • 14
  • 95
  • 141
  • @JHBonarius I don't see it as a duplicate. The other question is about how to use std::enable_if, not about whether to use it or something else. – ypnos Mar 22 '21 at 15:12
0

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.

Quimby
  • 17,735
  • 4
  • 35
  • 55