2

There have been a few questions about static data members of template classes not being initialized. Unfortunately none of these had answers that were able to help me with my specific problem.

I have a template class that has a static data member that must be instantiated explicitly for specific types (i.e., must be specialized). If this is not the case, using a different template function should cause a linker error.

Here's some code:

#include <iostream>

template <typename T>
class Instantiate {
public:
    static Instantiate instance;
private:
    Instantiate(std::string const &id) {
        std::cout << "Instantiated " << id << "." << std::endl;
        // Additional, important side effects...
    }
};

template <typename T>
void runme() {
    // Do something to ensure instance is instantiated,
    // without creating run-time overhead.
    // The following only works without optimization.
    void *force = &Instantiate<T>::instance;
}

// Instances need to be explicitly specialized for specific types.
template <> Instantiate<int> Instantiate<int>::instance = {"int"};

int main() {
    // This is OK, since Instantiate<int>::instance was defined.
    runme<int>();
    // This should cause a (linker) error, since
    // Instantiate<double>::instance is not defined.
    runme<double>();
}

Calling runme<T> should require that Instantiate<T>::instance is defined, without actually using it. Getting a pointer to instance as shown works - but only if no optimizations are enabled. I need a different method that works with at least O2, and also works if the instantiation of instance occurs in a different compilation unit.

Question: How can I ensure that I get a linker error when calling runme with a type T for which no explicit Instantiate<T>::instance has been defined/specialized?

M.M
  • 138,810
  • 21
  • 208
  • 365
zennehoy
  • 6,405
  • 28
  • 55
  • [Similar question](http://stackoverflow.com/questions/27136524/why-does-the-c-linker-allow-undefined-functions/) but with a function rather than a static variable – M.M Jan 23 '15 at 10:00

1 Answers1

2

If I understand your post correctly, your sample code can be reduced to:

struct X
{
    static int x;
};

int main()
{
    void *f = &X::x;
}

and you are finding that a link error is only generated if -O2 is not passed.


The One Definition Rule is extremely complicated but I'm fairly confident that &X::x counts as odr-use. However , [basic.def.odr]/4 says:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

The last 3 words is a big weasel clause for compilers, it basically permits the behaviour that you are seeing. The program is ill-formed (and so any executable generated has completely undefined behaviour) but the standard does not require that the compiler/linker produce any warnings or errors.

If the ODR rule didn't have this escape clause then the optimizer's job would be much more difficult; e.g. it could have identified that your function only contains dead code but it would have to have extra logic to check for all odr-use of things in the function.


So how do we fix this? Since all ODR violations for variables have the same "no diagnostic required" provision, there's no guaranteed solution. We will have to try and find something that your particular compiler likes, or a way to prevent optimization.

This worked for me with gcc 4.8.1:

void *volatile f = &X::x;

(and the same thing worked in your code sample). This will incur a small runtime penalty though (the compiler has to generate an instruction for the call to runme). Maybe someone else will come up with a nicer trick :)

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Effectively, yes. I included a bit more code than that to avoid answers that only work in this simple case (specifically when `X` is not a template class). – zennehoy Jan 23 '15 at 09:46
  • @zennehoy updated answer, hopefully it is some progress – M.M Jan 23 '15 at 09:57
  • That's not bad at all! I had tried something with volatile, but that might have been `volatile void *f` which of course didn't work :) This works with my gcc 4.9.2 as well, at least in the test case. Now to see if it works in the full code. – zennehoy Jan 23 '15 at 10:06
  • VS2013 also gives a linker error for `runme()` with the volatile pointer solution in release mode (but not for the original problem). – Pixelchemist Jan 23 '15 at 10:09
  • 1
    For posterity: Based on this answer I've found a way that compiles away completely. Simply create a function `template void require(T * volatile) { }`, and call it with a pointer to the instance: `require(&X::x);`. This also avoids the warning about an unused variable. The resulting binary is identical to when `require` is not used. Note that `require` has to be a template function. If it is a regular function taking a `void * volatile` instead, it will not cleanly compile away. – zennehoy Jan 23 '15 at 14:59