views:

248

answers:

2

A colleague of mine told me about a little piece of design he has used with his team that sent my mind boiling. It's a kind of traits class that they can specialize in an extremely decoupled way.

I've had a hard time understanding how it could possibly work, and I am still unsure of the idea I have, so I thought I would ask for help here.

We are talking g++ here, specifically the versions 3.4.2 and 4.3.2 (it seems to work with both).

The idea is quite simple:

1- Define the interface

// interface.h
template <class T>
struct Interface
{
  void foo(); // the method is not implemented, it could not work if it was
};

//
// I do not think it is necessary
// but they prefer free-standing methods with templates
// because of the automatic argument deduction
//
template <class T>
void foo(Interface<T>& interface) { interface.foo(); }

2- Define a class, and in the source file specialize the interface for this class (defining its methods)

// special.h

class Special {};


// special.cpp

#include "interface.h"
#include "special.h"

// 
// Note that this specialization is not visible outside of this translation unit
//
template <>
struct Interface<Special>
{
  void foo() { std::cout << "Special" << std::endl; }
};

3- To use, it's simple too:

// main.cpp

#include "interface.h"

class Special; // yes, it only costs a forward declaration
               // which helps much in term of dependencies

int main(int argc, char* argv[])
{
  Interface<Special> special;
  foo(special);
  return 0;
};

It's an undefined symbol if no translation unit defined a specialization of Interface for Special.

Now, I would have thought this would require the export keyword, which to my knowledge has never been implemented in g++ (and only implemented once in a C++ compiler, with its authors advising anyone not to, given the time and effort it took them).

I suspect it's got something to do with the linker resolving the templates methods...

  • Do you have ever met anything like this before ?
  • Does it conform to the standard or do you think it's a fortunate coincidence it works ?

I must admit I am quite puzzled by the construct...

+5  A: 

Thats pretty neat. I'm not sure if it is guaranteed to work everywhere though. It looks like what they're doing is having a deliberately undefined template method, and then defining a specialization tucked away in its own translation unit. They're depending on the compiler using the same name mangling for both the original class template method and the specialization, which is the bit I think is probably non-standard. The linker will then look for the method of the class template, but instead find the specialization.

There are a few risks with this though. No one, not even the linker, will pick up multiple implementations of the method for example. The template methods will be marked selectany because template implies inline so if the linker sees multiple instances, instead of issuing an error it will pick whichever one is most convenient.

Still a nice trick though, although unfortunately it does seem to be a lucky coincidence that it works.

Stewart
Well the linking issue arises for any template method definition, even though the risk is higher with specialization I admit. Here they rely on convention (the specialization is inside the corresponding source file) to prevent violation of the One Definition Rule. One could remark that if the template method was defined for the generic case, then ODR would be violated because the generic template would be instanciated. Good point on the name mangling issue, it had not occurred to me there could be a problem there as well.
Matthieu M.
+6  A: 

Like @Steward suspected, it's not valid. Formally it's effectively causing undefined behavior, because the Standard rules that for a violation no diagnostic is required, which means the implementation can silently do anything it wants. At 14.7.3/6

If a template, a member template or the 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.

In practice at least on GCC, it's implicitly instantiating the primary template Interface<T> since the specialization wasn't declared and is not visible in main, and then calling Interface<T>::foo. If its definition is visible, it instatiates the primary definition of the member function (which is why when it is defined, it wouldn't work).

Instantiated function name symbols have weak linkage because they could possibly be present multiple times in different object files, and have to be merged into one symbol in the final program. Contrary, members of explicit specializations that aren't templates anymore have strong linkage so they will dominate weak linkage symbols and make the call end up in the specialization. All this is implementation detail, and the Standard has no such notion of weak/strong linkage. You have to declare the specialization prior to creating the special object:

template <>
struct Interface<Special>;

The Standard lays it bare (emphasize by me)

The placement of explicit specialization declarations for function templates, class templates, member functions of class templates, static data members of class templates, member classes of class templates, member class templates of class templates, member function templates of class templates, member functions of member templates of class templates, member functions of member templates of non-template classes, member function templates of member classes of class templates, etc., and the placement of partial specialization declarations of class templates, member class templates of non-template classes, member class templates of class templates, etc., can affect whether a program is well-formed according to the relative positioning of the explicit specialization declarations and their points of instantiation in the translation unit as specified above and below. When writing a specialization, be careful about its location; or to make it compile will be such a trial as to kindle its self-immolation.

Johannes Schaub - litb
Thus if I understand correcly, it would be sufficient in "special.h" to include "interface.h" and to forward declare the template specialization `template <> struct Interface<Special>;` and then in "main.cpp" to include "special.h", so as to make the program well-formed, even though the definition of the `Interface<T>::foo` method would never appear ?
Matthieu M.
@Matthieu, exactly! That's how you can solve the misery
Johannes Schaub - litb
If I read this correctly, this means that any program (multiple translation units) where an explicit specialization of a template exists for a given set of parameters but in some other translation unit the implicit specialization is used for the same set of parameters is an ODR violation which the compiler is not required - and probably cannot - to issue a diagnostic for?Thats pretty scary and another good reason to always put partial specializations right with the primary template where possible.
Stewart
@Stewart yep you are reading it correctly! That's the issue exactly :)
Johannes Schaub - litb
Frerich Raabe