Search code examples
c++templatesspecializationgcc4

Template specialization ambiguity: either specialized or original method may be linked


Look at base::m_visible method in tps.hpp (it's «default»):

#include <sstream>
#include <iostream>

template <typename T> struct base
{
  void m_log() const;
  void m_visible() const;
};

struct inheritor: base<inheritor>
{
  void log() const;
};

template <typename T> void base<T>::m_log() const
{
  std::ostringstream os;
  m_visible();
  os.str(""); // We have «default/default» if comment here, :-0
  std::cout << os.str();
}

template <typename T> void base<T>::m_visible() const
{
  std::cout
    << "default" << std::endl
  ;
}

which specialized for inheritor struct in tps_spec.cpp (named «spec»):

#include "tps.hpp"

template <> void base<inheritor>::m_visible() const
{
  std::cout
    << "spec" << std::endl
  ;
}

void inheritor::log() const
{
  m_log();
}

and further called from tps_main.cpp:

#include "tps.hpp"

int main(int, char **argv)
{
  std::cout << argv[0] << ": ";
  inheritor().m_log(); // We have «spec/spec» if call inheritor::log
  return 0;
}

Result depends on order of compilation units (GCC 4.8.4):

g++ -Wall -O3 -o tps1 tps_spec.cpp tps_main.cpp && g++ -Wall -O3 -o tps2 tps_main.cpp tps_spec.cpp && ./tps1 ; ./tps2
./tps1: spec
./tps2: default

and this happens only with optimization -O3. Any experimental editions, marked by comments yields different results. Why?


Solution

  • Because this is undefined behavior.

    If a template is specialized, this specialization must be "visible" everywhere.

    You declare and implement the specialization in one translation unit. That specialized template declaration is, basically, completely internal to that translation unit and the specialization is only visible to that translation unit. Your tps_main translation unit has no visibility, and no knowledge whatsoever, of the template specialization; as such linking it together with a different translation unit, that has this template specialization visible, results in undefined behavior.

    You need to declare and define (with some exceptions that are not very relevant here) the specialization in the header file, so that every translation unit that includes the header file with the definition of the template will also have the definition of the specialization as well.

    A template specialization is not an "implementation" of the template, of some kind, for the specialized template instance. A template specialization is, basically, a completely independent class declaration. Therefore, if you have a translation unit that uses that class, it must have that class declared.

    You can't just declare a class in one translation unit, and expect to use it in a different translation unit without the other translation unit seeing that declaration.

    Basically, all your translation units that you are linking together must have the same class, object, and template declarations. This includes template specializations.

    This is just a round-about way of saying that C++ templates can only be portably defined in header files.