Search code examples
c++templatestemplate-specializationoverload-resolutionone-definition-rule

Does fully specialized template function violate ODR with a regular function?


I just realized this snippet compiles safely without any warnings on g++ and clang. (given --std=c++14 --Wall)

#include <iostream>

template <typename T>
void foo(const T& a, const T& b)
{
    std::cout << "1. Template version called.\n";
}

template <>
void foo(const int& a, const int& b)
{
    std::cout << "2. Template specialized version called.\n";
}

void foo(const int& a, const int& b)
{
    std::cout << "3. Regular function version called.\n";
}

int main()
{
    // Prints: 3. Regular function version called.
    foo(4, 2);
}

But I am not sure if, according to the standards, the 2. Template specialized version and 3. Regular function version violates ODR or not.

So, does this violate ODR?

If not, is it guaranteed that foo(4,2) always calls the 3. Regular function version?
Or is it compiler dependent?


Solution

  • Short answer: No it does not violate ODR and yes it is guaranteed to call the regular function, as long as you use an C++ standard compliant compiler.

    When calling foo, the compiler first makes a list of candidate functions, by looking up the name foo. On of the candidates is the regular function. It also goes through template type deduction and eventually finds your specialization to be a second candidate for overload resolution.

    After that the compiler produces a set of viable candidates by matching the number of arguments and testing if there is an implicit conversion between the arguments and the parameters you provided. Both the candidates (the regular function and the specialized template) pass this stage and are viable.

    Then, the compiler decides which of the viable candidates is the best, by following a set of rules which are described e.g. here. Normally it would take the candidate whose parameter types match the argument types best (rule 1.-3. as in the linked page). However because your parameter types are exactly the same, it goes to rule 4, which says that non-templated functions take precedence over template-specalizations. So the regular functions is picked!

    Now to come to your question regarding the ODR violation: The compiler assings the template a different symbol than the regular function. If you compile the program from above and look at the exported (mangled) symbols, you will see something like

    void foo<int>(int const&, int const&)
    foo(int const&, int const&)
    

    So both functions are exported and could also be called (according to the same rules as described above) from other translation units.

    Note One additional note on function template specialization. There are reasons to prefer overloading functions to specialized function templates. If providing a specialization where two or more templates could be the "parent", you can run into strange effects, where declaration order affects the actual outcome. You can find a more information on this topic and an example in the answer to this question.