13

I'm studying the behavior of the C++ linker with respect to template specializations. I'm using Microsoft Visual C++ 2010 for these experiments. I don't know if the behavior is the same with other toolchains (e.g. gcc).

Here's a first code snippet:

// bar.cpp

template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }

// main.cpp

template <typename T> int foo() { return 1; }
template <> int foo<double>() { return 2; }

int bar();

int main()
{
    const int x = bar();
    const int y = foo<double>();  // doesn't link
}

Expectedly, this code doesn't link because foo<double>() has multiple definitions as it gets instantiated once in bar.cpp and once in main.cpp (via specialization). We would then expect, if this program would link, that bar() and main() would use distinct instantiations of foo() such that at the end we would have x == 1 and y == 2.

Let's fix the link error by declaring the specialization of foo<double>() as static:

// bar.cpp

template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }

// main.cpp

template <typename T> int foo() { return 1; }
template <> static int foo<double>() { return 2; }  // note: static

int bar();

int main()
{
    const int x = bar();          // x == 1
    const int y = foo<double>();  // y == 2
}

We now have x == 1 and y == 2, as we expected. (Note: we must use the static keyword here: an anonymous namespace won't do since we can't specialize a template function in a different namespace than its declaration.)

Now, the use of the static keyword is rather unintuitive. Typically, the specialization foo<double>() would reside somewhere in a header file, and thus would be marked as inline, like in the following snippet:

// bar.cpp

template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }

// main.cpp

template <typename T> int foo() { return 1; }
template <> inline int foo<double>() { return 2; }  // note: inline

int bar();

int main()
{
    const int x = bar();          // x == 2
    const int y = foo<double>();  // y == 2
}

This code now links properly and when we run it we get x == 2 and y == 2. This is the bit I find surprising: why is there a single definition of foo<double>()? What is the meaning of inline in this code?

A last snippet:

// bar.cpp

template <typename T> int foo() { return 1; }
int bar() { return foo<double>(); }

// main.cpp

template <typename T> int foo() { return 1; }
template <> inline int foo<double>() { return 2; }  // note: inline

int bar();

int main()
{
    const int x = bar();             // x == 1
    // const int y = foo<double>();  // note: commented out
}

This case is actually not surprising: the specialization of foo<double>() is no longer instantiated in main.cpp (although the declaration is still there), so the only instantiation remaining is the one in bar.cpp.

François Beaune
  • 4,270
  • 7
  • 41
  • 65
  • 1
    In your third paragraph, the call in bar.cpp is not "implicit specialization", but rather "instantiation", since you only *call* `foo()` (you're not specializing anything). Also, next sentence, "explicit specialization" should probably just be "specialization" or "total specialization", to avoid confusion with the similar sounding "explicit instantiation" (which you don't have). – Kerrek SB Aug 25 '11 at 12:39
  • 1
    I think if you `inline` a function, you promise that it has the same definition in all translation units. If that's true, then you're incurring undefined behaviour. – Kerrek SB Aug 25 '11 at 12:47
  • @Kerrek Actually that's not true. Inline functions are allowed to have different implementations in different translation units. They are an exception from the One Definition Rule. – Šimon Tóth Aug 25 '11 at 12:53
  • @Let_Me_Be: No, they aren't. Each definition of such things must consist of the same sequence of tokens or you have undefined behavior. The toolchain is not required to give any diagnostic when some aspect of the one definition rule is violated. – David Hammen Aug 25 '11 at 13:04
  • @David Inline functions with same name but different definitions are considered separate entities and do not violate ODR. – Šimon Tóth Aug 25 '11 at 13:06
  • @Let_Me_Be: I think that template specialization takes priority over the `inline` or `static` here. The paragraph on template specialization makes no specific provisions for those. – Matthieu M. Aug 25 '11 at 13:10
  • @Matthieu Yes, this case is broken for a different reason. I was just responding to Kerrek. – Šimon Tóth Aug 25 '11 at 13:13
  • @Kerrek Thanks, I fixed the wording as you suggested in your first comment. – François Beaune Aug 25 '11 at 15:03
  • 1
    @Let_Me_Be: inline functions are not exempt from One Definition Rule. Function templates are, but not (full) function template specializations. – Maxim Egorushkin Aug 25 '11 at 15:20
  • @Maxim Yeah, you are right, I didn't realize that C++ doesn't have `extern inline` and `static inline` :-/ – Šimon Tóth Aug 25 '11 at 15:24

1 Answers1

15

You are actually violating a C++ rule here (emphasis mine):

14.7.3 [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. 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. An implicit instantiation is never generated for an explicit specialization that is declared but not defined. [ Example:

class String { };

template<class T> class Array { /* ... */ };
template<class T> void sort(Array<T>& v) { /* ... */ }

void f(Array<String>& v) {
  sort(v); // use primary template
  // sort(Array<T>&), T is String
}

template<> void sort<String>(Array<String>& v); // error: specialization
                                                // after use of primary template

template<> void sort<>(Array<char*>& v); // OK: sort<char*> not yet used

template<class T> struct A {
  enum E : T;
  enum class S : T;
};

template<> enum A<int>::E : int { eint }; // OK
template<> enum class A<int>::S : int { sint }; // OK
template<class T> enum A<T>::E : T { eT };
template<class T> enum class A<T>::S : T { sT };
template<> enum A<char>::E : int { echar }; // ill-formed,
                                            // A<char>::E was instantiated
                                            // when A<char> was instantiated
template<> enum class A<char>::S : int { schar }; // OK

end example ]

Community
  • 1
  • 1
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722