Question

I am writing a simple vector class and I would like to have some member functions that are only available in vectors of certain lengths (cross product for a 3 element vector for example). I stumbled across std::enable_if and it looks like it may be able to do what I want, but I don't seem to be able to get it working correctly.

#include <iostream>
#include <type_traits>

template<typename T, unsigned int L>
class Vector
{
    private:
        T data[L];

    public:
        Vector<T,L>(void)
        {
            for(unsigned int i = 0; i < L; i++)
            {
                data[i] = 0;
            }
        }

        T operator()(const unsigned int i) const
        {
            return data[i];
        }

        T& operator()(const unsigned int i)
        {
            return data[i];
        }

        Vector<typename std::enable_if<L==3, T>::type, L> cross(const Vector<T,L>& vec2) const
        {
            Vector<T,L> result;

            result(0) = (*this)(1) * vec2(2) - (*this)(2) * vec2(1);
            result(1) = (*this)(2) * vec2(0) - (*this)(0) * vec2(2);
            result(2) = (*this)(0) * vec2(1) - (*this)(1) * vec2(0);

            return result;
        }
}; 

int main(void)
{
    Vector<double,3> v1;
    Vector<double,3> v2;
    Vector<double,3> v3;
    //Vector<double,4> v4;

    v1(0) = 1;
    v1(1) = 2;
    v1(2) = 3;

    v2(0) = 4;
    v2(1) = 5;
    v2(2) = 6;

    v3 = v1.cross(v2);

    std::cout << v3(0) << std::endl;
    std::cout << v3(1) << std::endl;
    std::cout << v3(2) << std::endl;

    return 0;
}

The code above compiles and runs correctly, however if I uncomment the declaration of Vector<double,4> v4 I get the following error at compilation:

vec.cpp: In instantiation of ‘class Vector<double, 4u>’:
vec.cpp:46:22:   required from here
vec.cpp:29:59: error: no type named ‘type’ in ‘struct std::enable_if<false, double>’

Is someone able to point out where I am going wrong?

Was it helpful?

Solution

 template<unsigned LL = L>
  Vector<typename std::enable_if<LL==3 && L == 3, T>::type, LL>
  cross(const Vector<T,LL>& vec2) const
  {
    Vector<T,L> result;

    result(0) = (*this)(1) * vec2(2) - (*this)(2) * vec2(1);
    result(1) = (*this)(2) * vec2(0) - (*this)(0) * vec2(2);
    result(2) = (*this)(0) * vec2(1) - (*this)(1) * vec2(0);

    return result;
  }

PS. Why this works this way?

The definition of the variable v4 causes an implicit instantiation of the class template Vector, which causes, in turn, implicit instantiation of the declarations of class member functions, among other things (14.7.1 Implicit instantiation [temp.inst] #1). This latter instantiation, of course, results in an error.

If we instead change the member function to be a member template, according to the same clause, at this point the member template itself is instantiated and this instantiation looks, more or less, like:

template<unsigned LL = 3>
Vector<typename std::enable_if<LL==3 && 3 == 3, double>::type, LL>
cross(const Vector<double,LL>& vec2) const;

which is an entirely valid template declaration. We don't (and we cannot) perform any further instantiation at this point.

However, when we attempt to actually call cross, this is without doubt, "a context that requires the member/function definition to exist", therefore, according to (14.7.1 Implicit instantiation [temp.inst] #2, #3), the cross template (the second cross template, the one that is a result of the outer class template instantiation) is implicitly instantiated and the std::enable_if is given opportunity to do its work. As a side note, this is the situation, where the SFINAE principle is applicable.

PPS. To elaborate a bit further, although not directly connected with the OP question, but still worth mentioning that it's not always necessary to declare members as templates in order to handle similar situations.

There are situations, where a member of a class template is not "valid" for a given instantiation, but still the class template can be instantiated, for example:

#include <type_traits>

template<typename T>
struct S
{
  T x;

  T foo () const { return x; }

  typename std::remove_pointer<T>::type bar () const { return *x; }
};

S<int> x;
S<int *> y;

Apparently, in the instantiation S<int>, the expression *x is invalid, because the type of x is int. This program is correct, though. The important point is that during implicit instantiation only the declarations of the members are instantiated. In the above case, the instantiation S<int> causes the declaration int bar() const; to be instantiated, which is an entirely correct declaration.

Of course, if we later attempt to instantiate the definition of S<int>::bar, like in:

void f()
{
  x.foo ();
  //  x.bar (); // error
  y.foo ();
  y.bar ();
}

we will get an error.

(This still follows from the above-mentioned two paragraphs of the C++standard)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top