Question

I noticed that template specializations in .cpp files are discarded if compiler optimizations are enabled. I found this in a big application and I boiled down the problem to a simple example.

First, I define a new class in obj.h

#ifndef _OBJ_H_
#define _OBJ_H_

class Obj { };

#endif //_OBJ_H_

Then I define a new template function in templates.h

#ifndef _TEMPLATES_H_
#define _TEMPLATES_H_

template<typename T>
int get()
{
    return 0;
}

#endif //_TEMPLATES_H_

...and a specialization for the class Obj in templates.cpp

#include "templates.h"
#include "obj.h"

template<>
int get<Obj>()
{
    return 1;
}

Then I call the function from main:

#include <stdio.h>
#include "templates.h"
#include "obj.h"

int main()
{
    printf("Get: %d\n", get<Obj>());
    return 0;
}

Compiling this example with different -O levels yields different outputs.

$ g++ -o a main.cpp templates.cpp -O0
$ ./a
Get: 1

$ g++ -o a main.cpp templates.cpp -O2 #same with -O3, -O4, Os
$ ./a
Get: 0

The same happens replacing g++ with clang. I'm using g++ 4.7.2 and clang 3.4.
I'm no assembly expert, but looking at the generated code I can see that the -O0 version defines the mangled symbol _Z3getI3ObjEiv, which refers to the specialization, while the optimized versions just inline everything (as I expected).
The problem was eventually solved moving all the specializations to the header files, but still I'm curious: why this happens? Initially I thought that I hit an undefined behaviour, although it's strange that both clang and g++ produce the same results if it's the case.

Was it helpful?

Solution

Using a specialization which is not visible at the POC - point of call - is an error.

By the way compilers are not required to treat this as an error and can handle it as they like.

You should have the specialization into the header file but in C++11 you can do it into an external unit with the C++11 "extern" keyword for explicit instantiation declaration

As noted by Sebastian: it's a violation of the requirement in 14.7.3/6:

"If a 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."

Since no diagnostic is required, violating this requirement is undefined behavior.

OTHER TIPS

Like any function, the specialisation must be declared before use, in every translation unit that uses it. Otherwise, the compiler doesn't know that it exists, and will use the generic template (instantiating it if necessary).

This might mean that you end up with two non-identical copies of the specialisation (the one you wrote, and the one instantiated from the template); depending on exactly what the linker does, this might result in one or the other being called, or a link error, or some other undefined behaviour.

Note that you don't need to move the definition of the specialisation into the header; it's enough to declare it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top