Reputation: 6152
I have not kept up with the intricacies of templates so just know enough to be dangerous. However, I ran to a problem (reported by one of our testers) where code compiled properly on Xcode (Clang) and Visual Studio 2019 but the behavior was different and I'm wondering which one was "correct"
Consider the following simple class:
template <class T>
class foo
{
void set(int index, T value);
};
That default templated set method worked fine for most values of type T but I needed to specialize the behavior for a particular type, call it String.
So in the associated CPP file, I added the following:
template <>
void foo<String>::set(int index, String value)
{
// code here
};
Under Xcode, this worked perfectly fine. A call to set with a second parameter of String correctly invoked that specialized template.
However, the exact same code, compiled with Visual Studio failed, behaved differently. That same call with the String parameter just invoked the original set rather than than the specialized version.
I'm assuming that under Xcode, the compiler properly created a call with a signature that matched the specialized version that was available in the CPP file and hence was linked properly.
So my questions are:
Incidentally, I did try creating an explicit signature in the header file:
template<>
void foo<String>::set(int index, String value );
and Xcode didn't complain about it and everything still worked. However, Visual Studio, while not complaining about that header signature, did complain at link time that it couldn't find a matching implementation and so:
While I did find a way to do it that worked for both compilers (essentially putting inline versions in the header file, I would really like to understand why this problem happened.
I thank people in advance.
Edit: bllow is a real example I just put together. This compiles and runs perfectly fine under Xcode (in particular, the string specialization is correctly called for variable g) but fails to link in Visual Studio 2019 with the following error
( public: void __cdecl Foo<class std::basic_string<char,struct std::char_traits,class std::allocator > >::print(int,class std::basic_string<char,struct std::char_traits,class std::allocator >)" (?print@?$Foo@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@@QEAAXHV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) referenced in function main) referenced in function main )
Classes.h
---------
#pragma once
#include <iostream>
#include <string>
template <class T>
class Foo
{
public:
void print(int index, T value)
{
std::cout << "Using implicit template\n";
}
};
Classes.cpp
-----------
#include "Classes.h"
template<>
void Foo<std::string>::print(int i, std::string xyz)
{
std::cout << "Using explicit String template\n";
}
Main.cpp
--------
#include <string>
#include <iostream>
#include "Sub/Classes.h"
//==============================================================================
int main (int argc, char* argv[])
{
Foo<double> f;
f.print(1, 2.0);
Foo<std::string> g;
g.print(1, "abc");
return 0;
}
Upvotes: 0
Views: 95
Reputation: 2040
You need to forward declare the full specialization in the .h
file to tell other translation units not to instantiate the general template.
template <typename T> struct foo {
void set(int, T);
};
template <>
void foo<String>::set(int, String);
If you don't do this, you're violating the One Definition Rule. This happens because, when compiling other .cpp files, the compiler doesn't know that a full specialization exists, and instantiates the general template.
Say in bar.cpp
you write foo<String>().set(1, {});
. The compiler constructs that function from the definition of the general template and emits it in the .o
or .obj
file. At link time, the linker sees multiple versions of foo<String>::set
. It assumes they are equivalent and picks one at random. You can't know in general which it will pick. What's worse is that some calls to set
could have been inlined, and the version called there could be different from the one the linker picked. This is why ODR violations are so dangerous.
Putting the forward declaration template <> void foo<String>::set(int, String);
in the header tells all translation units "don't automatically instantiate this template, I promise to put this definition in some .cpp file." Now bar.cpp
doesn't instantiate the template. It calls foo<String>::set
as it would any other forward-declared function. foo.cpp
provides the definition for that full specialization, and it is used everywhere.
Upvotes: 0