I have templated functions in my C++11 Xcode project and some of them have specializations. However, I have discovered that the specializations only get called in debug builds; if I build in release, they are ignored.
I have successfully created a very simple example:
special.h
#include <cstdio>
struct special
{
template<typename T>
void call(const T&) { puts("not so special"); }
};
special.cpp
#include "special.h"
#include <string>
template<>
void special::call(const std::string&) { puts("very special"); }
main.cpp
#include "special.h"
#include <string>
int main()
{
std::string str = "hello world";
special s;
s.call(123);
s.call(str);
}
You can download the project (until somewhere this summer of 2013 at least) to reproduce the issue if you don't want to create it yourself. First run the project with the debug configuration, then run it again in release. The output that I expect is:
not so special
very special
And this is indeed what I get with the Debug build configuration. However, with Release, I get this:
not so special
not so special
Which means the specialized implementation of special::call
in special.cpp was ignored.
Why is the result inconsistent? What should I do to ensure that the specialized function is called in release builds?
Moral #1: If you want to customize a function base template and want that customization to participate in overload resolution (or, to always be used in the case of exact match), make it a plain old function, not a specialization. And, if you do provide overloads, avoid also providing specializations.
The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation. The definition created from a template instantiation is called a specialization.
Template in C++is a feature. We write code once and use it for any data type including user defined data types. For example, sort() can be written and used to sort any data type items. A class stack can be created that can be used as a stack of any data type.
Your program has UB. An explicit specialisation or at least its declaration must be visible before being used. [temp.expl.spec]§6:
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.
Add this declaration to special.h
:
template<>
void special::call(const std::string&);
Alternatively, you can put the specialistation itself into the header. However, as a specialisation is no longer a template, it follows normal function rules and must be marked inline
if placed in a header.
Also, be careful that function template specialisations have rather specific behaviour, and it's generally better to use overloads than specialisations. See Herb Sutter's article for details.
You violated the one definition rule (ODR). So what happens exactly? In main.cpp
there is no specialization known for special::call<string>
. Therefore the compiler generates an instantiation of the template into that translation unit (TU) that outputs "not so special". In special.cpp
there is a full specialization declared and defined, so the compiler puts that definition into the other translation unit. So you have two different definitions of the very same function in two different translation units, which is a violation of the ODR which means it is undefined behavior.
In theory, the outcome can be anything. A compiler error, a crash, a silent online order for a pizza, anything. Even different behavior in debug and release compiles.
In practice, I guess the following happens: When linking the debug build, the Linker sees the same symbol defined twice in the two TUs, which is allowed only for templates and inline functions. Because of the ODR it may assume that both definitions are equivalent and picks the one from special.cpp
, so you get by coincidence the behavior you expect.
During release build, the compiler inlines the call to special::call<string>
during the compilation of main.cpp
, so you get the only behavior seen in that TU: "not so special".
So how can you fix this?
In order to have only one definition for that specialization, you have to define it in one TU as you did, but you have to declare that there is a full specialization in any other TU, meaning declare that the specialization exists in the header special.h
:
// in special.h
template<>
void special::call(const std::string&);
Or, which is seen more often, define it in the header, so it's seen in every TU. Since fully specialized function templates are normal functions, you will have to define it inline:
// in special.h
template<>
inline void special::call(const std::string&)
{
puts("very special");
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With