First, a couple of solutions. For both of them, the essential part is taking the address of registrar from code that is guaranteed to be instantiated. This ensures that the definition of the static member is also instantiated, triggering the side effect.
The first one relies on the fact that the definition of the default constructor for each specialization of Foo is instantiated to handle the default initialization of a, b and c in main:
template<typename T> class Foo
{
public:
Foo() { (void)&RegistrarWrapper<Foo<T>>::registrar; }
};
A disadvantage is that this introduces a non-trivial constructor. An alternative that avoids this issue is the following:
template<class T> constexpr std::size_t register_class()
{
(void)&RegistrarWrapper<T>::registrar;
return 1;
}
template<typename T> class Foo
{
static char reg[register_class<Foo<T>>()];
};
The key here is to trigger the instantiation in the declaration of the static member, without relying on any initializer (see below).
Both solutions work fine in Clang 3.7.0, GCC 5.2.0 and Visual C++ 2015, both with and without optimizations enabled. The second one uses the extended rules for constexpr functions, which are a C++14 feature. Of course, there are several easy ways to make it C++11 - compliant, if needed.
I think the problem with your solution is that there's no guarantee that the initializer for __reg_ptr gets instantiated if its value is not used somewhere. Some Standard quotes from N4527:
14.7.1p2:
[...] the initialization (and any associated side-effects) of a static data
member does not occur unless the static data member is itself used in
a way that requires the definition of the static data member to exist.
This doesn't quite address the constexpr case, since (I think) it's talking about the out-of-class definition of a static data member that is odr-used (it's more relevant to registrar), but it's close.
14.7.1p1:
[...] The implicit instantiation of a class template specialization
causes the implicit instantiation of the declarations, but not of the
definitions, default arguments, or exception-specifications of the
class member functions, member classes, scoped member enumerations,
static data members and member templates [...]
This guarantees that the second solution works. Note that it doesn't guarantee anything about an in-class initializer for a static data member.
There seems to be some uncertainty regarding instantiation of constexpr constructs. There's CWG 1581, which is not that relevant to our case, except that, at the very end, it talks about the fact that it's not clear whether constexpr instantiation happens during constant expression evaluation or during parsing. Some clarifications in this area may provide some guarantees for your solution as well (either way...) but we'll have to wait.
A third variant: a way to make your solution work is to explicitly instantiate the specializations of Foo, instead of relying on implicit instantiation:
template class Foo<int>;
template class Foo<bool>;
template class Foo<std::string>;
int main()
{
for(auto&& data : test_vector()) {
std::cout << data.name() << std::endl;
}
}
This also works in all three compilers, and relies on 14.7.2p8:
An explicit instantiation that names a class template specialization
is also an explicit instantiation of the same kind (declaration or
definition) of each of its members [...]
Given that those are explicit instantiation definitions, this seems to be enough to convince GCC to instantiate the initializer for __reg_ptr. However, those explicit instantiation definitions can only appear once in the whole program ([14.7p5.1]), so extra care is needed. I consider the first two solutions to be more reliable.