3

Consider the following minimal example which reproduces a problem in a much bigger project:

spec.h:

#include <iostream>

class A
{
public:
    template<typename T>
    T test(const std::string& a)
    {
        std::cout << "DEFAULT CALLED WITH " << a << "\n";
        return T();
    }
};

other.cpp:

#include "spec.h"

template<>
float A::test<float>(const std::string& a)
{
    std::cout << "SPECIAL CALLED WITH " << a << "\n";
    return float();
}

spec.cpp:

#include <iostream>
#include "spec.h"

int main()
{
    A a;
    a.test<int>("int");
    a.test<float>("float");
    return 0;
}

compilation:

$ make
rm -f *.o lib.a output
clang++ -g other.cpp -c
clang++ -g spec.cpp -c
ar cr lib.a other.o
clang++ -g -o output lib.a spec.o
rm -f *.o output2
clang++ -g other.cpp -c
clang++ -g spec.cpp -c
clang++ -g -o output2 other.o spec.o

$ ./output
DEFAULT CALLED WITH int
DEFAULT CALLED WITH float

$ ./output2
DEFAULT CALLED WITH int
SPECIAL CALLED WITH float

question:

Why is this happening? is it getting stripped somehow? what is the difference between lib.a and direct object file usage? :-)

Thanks!

Alex Kremer
  • 1,876
  • 17
  • 18
  • I'm pretty sure you're in undefined behavior land here, violating the ODR. I'll look for the rule from the Standard that you're violating. – Ben Voigt Oct 01 '12 at 21:51
  • Just as a point of comparison, g++ and MSVC complain that `float A::test(std::string const&)` is defined multiple times when linking both `spec.o` and `other.o` explicitly. I'm a little surprised `clang++` doesn't as well. – Michael Burr Oct 01 '12 at 22:01

3 Answers3

8

From section 14.7.3p6:

If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required. If the program does not provide a definition for an explicit specialization and either the specialization is used in a way that would cause an implicit instantiation to take place or the member is a virtual member function, the program is ill-formed, no diagnostic required.

Your program is ill-formed because you used the specialization in spec.cpp without declaring it first in that translation unit. Or, as the following paragraph says:

The placement of explicit specialization declarations for function templates, class templates, member functions of class templates, static data members of class templates, member classes of class templates, member enumerations of class templates, member class templates of class templates, member function templates of class templates, member functions of member templates of class templates, member functions of member templates of non-template classes, member function templates of member classes of class templates, etc., and the placement of partial specialization declarations of class templates, member class templates of non-template classes, member class templates of class templates, etc., can affect whether a program is well-formed according to the relative positioning of the explicit specialization declarations and their points of instantiation in the translation unit as specified above and below.

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.

which I vote for as the awesomest paragraph limerick in the whole Standard.

Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
2

Ben Voigt's answer is correct, but I want to add a little to it.

You are essentially getting two different versions of the function, one in other.o and one in spec.o (generated by the inline template). The linker is designed to choose one and only one, making the assumption that they're both identical as the standard requires. In the first case, the linker will only pull a definition from a library if the symbol isn't already defined. Since it's defined in spec.o the library definition isn't used.

Community
  • 1
  • 1
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • @BenVoigt, if the program *was* well formed, the linker would be required to consolidate the duplicate identical functions into one. It attempts to perform the same function even when the functions aren't identical. I am merely trying to explain the observed behavior. I'll see if I can reword it to be more acceptable. – Mark Ransom Oct 01 '12 at 22:09
  • Mark, this is a great answer. Thanks. I would accept this answer but Ben solved my problem with a comment as well as explained what was going on (even though his explanation is not as clear). – Alex Kremer Oct 01 '12 at 22:19
  • 1
    @AlexKremer: The thing to note is that this is probably what clang++ actually did, but the Standard doesn't require it, and it could change in the next version. As much as possible you should try to make sure your code follows the rules of the Standard, and then it will work with any version of any compiler. – Ben Voigt Oct 01 '12 at 23:00
  • 1
    @AlexKremer, to emphasize what Ben said you probably had a 50/50 chance of getting the desired behavior even with the second example - you got lucky. – Mark Ransom Oct 01 '12 at 23:09
1

With the definition in the header each translation unit can create its own instantiation. Thus, there is never an undefined symbol referencing your specialized version. Correspondingly, the object file with the specialized version isn't included when looking at the library: it doesn't define any undefined symbol. When including the object file explicitly while linking, the linker has no choice than including it. However, you need to declare all specializations: without the declaration the compiler has no clue that the general version isn't applicable. What happens do this version, whether it is used or not, thus, depends on the way the symbol is treated.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380