9

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?

rozzy
  • 2,828
  • 6
  • 25
  • 35
  • possible duplicate of [Can I use boost::enable\_if on a member function?](http://stackoverflow.com/questions/4880922/can-i-use-boostenable-if-on-a-member-function) – Bo Persson Dec 09 '12 at 10:12
  • 1
    You probably want `typename std::enable_if>::type` rather than `Vector::type, L>`. – Billy ONeal Dec 09 '12 at 10:15
  • 1
    (You probably want your const `operator()` to return `T const&` too. Current code lets clients do something like `Vector v; Vector const& const_v = v; const_v(2) = 42.0; // Note that the write of 42 happens to a temporary. Not what the user expected`) – Billy ONeal Dec 09 '12 at 10:23

1 Answers1

9
 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)

chill
  • 16,470
  • 2
  • 40
  • 44
  • Thanks, although I do not quite understand why this is different from what I had. Is it just because the individual member function has to be templated as well? – rozzy Dec 09 '12 at 10:29
  • @aero117, no code is generated for a templated function until it is instantiated. In your code, the compiler will generate code for for the entire class(when template is instantiated), including the function. But when you make a function as template, code for this function will be generated only when this template is instantiated, for example, by calling the function. – awesoon Dec 09 '12 at 11:16
  • Kudos for a full-featured explanation! – Matthieu M. Dec 09 '12 at 13:34