Search code examples
c++templatesg++

Error when changing order of operators with templates in C++


I am writing the following templated vector class.

vectors.h:

#ifndef INCLUDE_HEADERS_VECTORS_H
#define INCLUDE_HEADERS_VECTORS_H

template<class Scalar> class Vector2D;
template<class Scalar> Vector2D<Scalar> operator+(Scalar a, Vector2D<Scalar> v);

template<class Scalar>
class Vector2D {
    public:
        Vector2D();
        Vector2D(Scalar a, Scalar b);

        Scalar getX();
        Scalar getY();

        Vector2D<Scalar> operator+(Scalar a);        
        friend Vector2D<Scalar> operator+ <>(Scalar a, Vector2D<Scalar> v);
        
    private:
        Scalar x, y; 

};

#endif 

vectors.cpp:

#include<headers/vectors.h>

template<class Scalar>
Vector2D<Scalar>::Vector2D() {
    x = 0;
    y = 0;
}

template<class Scalar>
Vector2D<Scalar>::Vector2D(Scalar a, Scalar b) {
    x = a;
    y = b;
}

template<class Scalar>
Scalar Vector2D<Scalar>::getX() {
    return x;
}

template<class Scalar>
Scalar Vector2D<Scalar>::getY() {
    return y;
}

template<class Scalar>
Vector2D<Scalar> Vector2D<Scalar>::operator+(Scalar a) {
    return Vector2D<Scalar>(a + x, a + y);
}

template<class Scalar>
Vector2D<Scalar> operator+(Scalar a, Vector2D<Scalar> v) {
    return Vector2D<Scalar>(a + v.x, a + v.y);
}

template class Vector2D<float>;
template Vector2D<float> operator+<float>(float a, Vector2D<float> v);

Main.cpp:

#include<headers/vectors.h>
#include<iostream>

int main(int argc, char* argv[]){
    Vector2D<float> a(1.0, 3.0);
    std::cout<<a.getX()<<" "<<a.getY()<<std::endl;

    a = (float)2.0 + a;
    std::cout<<a.getX()<<" "<<a.getY()<<std::endl;

    a = a + (float)-2.0;
    std::cout<<a.getX()<<" "<<a.getY()<<std::endl;

    return 0;
}

When I try to compile it with g++ on Windows, I get the following error:

g++ -Iinclude -Iinclude/SDL2 -Iinclude/headers -Llib -o Main src/*.cpp -lmingw32 -lSDL2main -lSDL2 -lSDL2_image
In file included from src/Main.cpp:2:
include/headers/vectors.h:29:33: error: declaration of 'operator+' as non-function
   29 |         friend Vector2D<Scalar> operator+ <>(Scalar a, Vector2D<Scalar> v);
      |                                 ^~~~~~~~
include/headers/vectors.h:29:41: error: expected ';' at end of member declaration
   29 |         friend Vector2D<Scalar> operator+ <>(Scalar a, Vector2D<Scalar> v);
      |                                         ^
      |                                          ;
include/headers/vectors.h:29:43: error: expected unqualified-id before '<' token
   29 |         friend Vector2D<Scalar> operator+ <>(Scalar a, Vector2D<Scalar> v);
      |                                           ^
In file included from src/vectors.cpp:1:
include/headers/vectors.h:29:33: error: declaration of 'operator+' as non-function
   29 |         friend Vector2D<Scalar> operator+ <>(Scalar a, Vector2D<Scalar> v);
      |                                 ^~~~~~~~
include/headers/vectors.h:29:41: error: expected ';' at end of member declaration
   29 |         friend Vector2D<Scalar> operator+ <>(Scalar a, Vector2D<Scalar> v);
      |                                         ^
      |                                          ;
include/headers/vectors.h:29:43: error: expected unqualified-id before '<' token
   29 |         friend Vector2D<Scalar> operator+ <>(Scalar a, Vector2D<Scalar> v);
      |                                           ^
src/vectors.cpp:70:18: error: non-class, non-variable partial specialization 'operator+<Scalar>' is not allowed
   70 | Vector2D<Scalar> operator+<Scalar>(Scalar a, Vector2D<Scalar> v) {
      |                  ^~~~~~~~~~~~~~~~~
src/vectors.cpp: In instantiation of 'Vector2D<Scalar> operator+(Scalar, Vector2D<Scalar>) [with Scalar = float]':
src/vectors.cpp:96:69:   required from here
src/vectors.cpp:71:35: error: 'float Vector2D<float>::x' is private within this context
   71 |     return Vector2D<Scalar>(a + v.x, a + v.y);
      |                                 ~~^
include/headers/vectors.h:37:16: note: declared private here
   37 |         Scalar x, y;
      |                ^
src/vectors.cpp:71:44: error: 'float Vector2D<float>::y' is private within this context
   71 |     return Vector2D<Scalar>(a + v.x, a + v.y);
      |                                          ~~^
include/headers/vectors.h:37:19: note: declared private here
   37 |         Scalar x, y;
      |                   ^
mingw32-make: *** [MakeFile:2: all] Error 1

However, if I change the order of the declarations of the + operator in vectors.h like in the following, it will work just fine:

#ifndef INCLUDE_HEADERS_VECTORS_H
#define INCLUDE_HEADERS_VECTORS_H

template<class Scalar> class Vector2D;
template<class Scalar> Vector2D<Scalar> operator+(Scalar a, Vector2D<Scalar> v);

template<class Scalar>
class Vector2D {
    public:
        Vector2D();
        Vector2D(Scalar a, Scalar b);

        Scalar getX();
        Scalar getY();

        friend Vector2D<Scalar> operator+ <>(Scalar a, Vector2D<Scalar> v);
        Vector2D<Scalar> operator+(Scalar a);        
        
        
    private:
        Scalar x, y; 

};

I am completely lost as to why this happens.

I tried to remove the templates and the problem goes away, so it must be related.


Solution

  • The issue comes from how the "word <" sequence is understood by the compiler.

    It can be "word less_than" or "word start_of_template_parameters". Most often there is no ambiguity. But here we are in the complex case where the word is identified as a function name.
    The problem with functions is that:

    • a unique name can designate several possible functions.
    • and some of these functions, although visible, must be hidden (for example functions that would be in a base class).

    It's why we must avoid to have a same name as template and as non-template.

    2nd case, the friend is declared before the operator+.
    The only function seen before is of type template, the compiler has no choice, it must interpret the < as the start of template parameters.

    1st case, the friend is declared after the operator+.
    There is a function operator+ which is the most visible and not template. Although a template version is possible, the non-template version is assumed to be valid. The way to interpret "word <" is therefore "word less_than". Hence the error.

    Since C++20, the standard has evolved to handle more cases. This problem no more exist for the clang compiler, I don't know for the others.

    Just some notes about the code:

    • template programming is complex for a beginner
    • the const-correctness is important to know and understand. Your code has errors about that.
    • prefer use double over float. It has more precision and is often the fastest one. double is the standard type for floating values.
    • the initialization list of the constructor is also a base to know.
    • define template in cpp files is possible but rare and you may be in that case. So here you must export all possible cases. You did it for float, you can add double and long double.
    • when we define operators, for those which can be member or non-member. A rule is for all with identical or symmetrical parameter types, always prefer the non-member version. Here for vec+N friend is needed, but for N+vec it's best to also use template friend. considering that, you would never see the current issue!