0

I have this class (.h file):

class Entity {

public:

    template<typename T, typename... Args>
    T& addComponent(Args &&... args) {
        // add component and return
    }

};

When I get an specific type, i want a different behavior, so I'm triying to overload the "addComponent" method like this (.cpp file):

template<>
TransformComponent& Entity::addComponent<TransformComponent>() {
    printf("overloaded! \n");
    // add component and return
}

the method call is made like this:

entity.addComponent<TransformComponent>();

but the method overload is never called.

edit 1:

I have moved the method declaration to the header file, has sugested.

class Entity {

public:

    template<typename T, typename... Args>
        T& addComponent(Args &&... args) {
        // add component and return
    }
    
    template<>
    TransformComponent& addComponent<TransformComponent>() {
        // add component and return
    }
};

But now i have another problem, a compile error (explicit specialization in non-namespace scope). Apparently there are solutions for this problem, but not very good ones (link in the comments).

edit 2:

@aschepler 's solution worked for me (working on gcc and clang). For some reason my first edit on this post is working/compiling on clang, and I'm not shure why.

  • 2
    Does this answer your question? [Why can templates only be implemented in the header file?](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file) – ChrisMM Mar 08 '21 at 02:15
  • 1
    Your `addComponent` "overload" is declared in a `.cpp` file, not a header - so the rest of your program won't see it. You need to understand how templates are compiled and instantiated first. – Dai Mar 08 '21 at 02:15
  • The declaration in the cpp file indeed does not work. But moving the method to the header file gives me a compile error (explicit specialization in non-namespace scope). Apparently there are solutions for this problem, but not very good ones (https://stackoverflow.com/questions/2097811/c-syntax-for-explicit-specialization-of-a-template-function-in-a-template- clas) . – Matheus Zickuhr Mar 08 '21 at 03:21
  • Shouldn't the specialization have a parameter of type `TransformComponent&&`, rather than zero parameters? – aschepler Mar 08 '21 at 05:11
  • 1
    @aschepler: Empty pack for `Args...`. – Jarod42 Mar 08 '21 at 09:46
  • @MatheusZickuhr: You are looking for **specialization**, not **overload**. – Jarod42 Mar 08 '21 at 09:47

1 Answers1

3

The trouble with the original code is that when code wants to use a specialization of that template function, it must know that the explicit specialization exists. If the explicit specialization is only found in one translation unit (generally meaning a single *.cpp file plus included headers), then it's invalid for a different translation unit to use that specialization, since the compiler doesn't ordinarily know at that point that it shouldn't just use the general definition.

So the header has to have a declaration of the explicit specialization, which might or might not be a definition.

According to C++14 and earlier, an explicit specialization declaration can only appear at namespace scope, never inside a class definition except as a friend. In C++17 and later, an explicit specialization can be declared anywhere its primary template can, so the code after your first edit is valid.

The clang++ compiler allows the specialization to be declared and/or defined in class scope no matter what -std option is in effect. (This is safe since it couldn't possibly mean something different in older versions.) The g++ compiler has not yet implemented the newer more permissive rule at all; this is GCC bug 85282.

So if the code must work with g++, you need a declaration in the same header file, outside the class definition. Best practice is to declare any specializations nearly as soon as possible, to minimize chances any code would attempt to use the specialization before it's declared. Sometimes that means soon after the primary template is declared and/or defined, and sometimes that means soon after some type used as a template argument is defined. Here it means soon after the class containing the template is defined.

class Entity {
public:

    template<typename T, typename... Args>
        T& addComponent(Args &&... args) {
        // add component and return
    }
};
template<>
inline TransformComponent& Entity::addComponent<TransformComponent>() {
    // add component and return
}

Or it doesn't actually need to be inline and in the header. Since there are no template parameters, you can declare it in the header and define it in a source file:

// HEADER
class Entity {
public:

    template<typename T, typename... Args>
        T& addComponent(Args &&... args) {
        // add component and return
    }
};
template<>
TransformComponent& Entity::addComponent<TransformComponent>();

// SOURCE
#include "Entity.hpp"

template<>
TransformComponent& Entity::addComponent<TransformComponent>() {
    // add component and return
}

(The Q&A you linked in the comments is about a function template which is a member of a class template, when one wants to specialize the function template but as a member of the general class template. That can't be done directly. Since your function template is a member of a non-template class, things are simpler.)

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • And as the old limerick goes: When writing a specialization / Be careful about its location, / Or to make it compile / Will be such a trial / As to kindle its self-immolation. – aschepler Mar 08 '21 at 05:13
  • Your solution worked, thanks. Do you happen to know why my code in the first edit compiles on clang? – Matheus Zickuhr Mar 08 '21 at 14:07
  • Oh, I see the rule has changed in C++17, to make the specialization in class scope legal. But clang++ applies the new rule no matter what `-std` option is used, and g++ hasn't yet implemented the new rule at all. – aschepler Mar 08 '21 at 17:05