6

Currently I am working on a C++ project in which I plan to embed Lua scripts. For that reason certain classes need to be exported to Lua and I wanted to make this more convenient therefore I created a template class:

template <class T>
class ExportToLua {
    public:
        ExportToLua() {}
        ~ExportToLua() {}
    private:
        static int m_registered;
};
template <class T> int ExportToLua<T>::m_registered = T::exportToLua();

Now every class that needs to be exported is derived from ExportToLua<T> with T="the class to be exported". Example:

 class Example: public ExportToLua<Example> {
 public:
     Example();
     virtual ~Example();
     static int exportToLua();
 private:
 };

where Example's static member function exportToLua() holds the class-specific registration code. My understanding is that an instance of the static member variable ExportToLua<T>::m_registered exists for every compile unit - that is - for every T.

But when I start my program the registration code never gets called. For example in example.cpp:

 int Example::exportToLua() {
     std::cout << "int Example::exportToLua()" << std::endl;
     return -2;
 }

however I never see this message when I run my program.

Any idea why? Is the compiler some "optimizing away" the static variable m_registered, because I am not using it anywhere?

Thanks for your input,

Best, Christoph

chris.schuette
  • 307
  • 2
  • 11
  • Just guessing... Try replace private with protected when declaring the m_registered. – Zoka Sep 02 '13 at 10:15
  • Thanks, but no, that doesn't change anything - just tried. – chris.schuette Sep 02 '13 at 10:16
  • What do you mean by "the registration code never gets called"? What is the "symptom"? – piwi Sep 02 '13 at 10:19
  • 2
    Perhaps you must explicitly instantinate the base, i.e. `template class ExportToLua;` in the compilation unit (source file) of `class Example`. – Walter Sep 02 '13 at 10:23
  • in my main.cpp I instantiate class Example. Examples constructor calls ExportToLua's ctor. Is this what you are referring to? – chris.schuette Sep 02 '13 at 10:26
  • 3
    Answer here: http://stackoverflow.com/a/17132624/688659 . Live example: http://ideone.com/4kTHYg (vs http://ideone.com/N5wJNE ) – gx_ Sep 02 '13 at 10:26
  • 1
    @gx_ this answer is far from satisfying. It refers to a particular compiler and makes not statement about the legality/correctness of the code. – Walter Sep 02 '13 at 10:29
  • I still would like to have an appropriate answer ... is this a bug of gcc (and whatever other compiler you used)? – Walter Sep 02 '13 at 10:30
  • Well, it seems to work for gcc as well. – chris.schuette Sep 02 '13 at 10:30
  • @Walter Admittedly, I'm not a language lawyer, but feel free to dig into the Standard =) – gx_ Sep 02 '13 at 10:31
  • 2
    14.7.1p1 ... The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions or default arguments, of the class member functions, member classes, scoped member enumerations, static data members and member templates; and it causes the implicit instantiation of the definitions of unscoped member enumerations and member anonymous unions. – chris.schuette Sep 02 '13 at 10:36
  • I can't say that I like the standard in this respect. This leads to weird behaviour. But I am not an expert on language design. – chris.schuette Sep 02 '13 at 10:38
  • @chris.schuette You should probably add it as an answer. It barely fits as a comment. – greatwolf Sep 02 '13 at 10:39
  • 1
    Thanks for the Standard quote! @Walter here you are =) (I can now add this authoritative reference to my answer on the other question). I think you can post an answer to your own question. – gx_ Sep 02 '13 at 10:40
  • @greatwolf agreed. But essentially gx_ gave the answer, I only dug up the passage, and I am waiting for him to post and approve it. If that does not happen I will post the answer myself. – chris.schuette Sep 02 '13 at 10:41

2 Answers2

4

If the compiler implicitly instantiates a class template that contains static members, those static members are not implicitly instantiated. The compiler will instantiate a static member only when the compiler needs the static member's definition.

This behaviour is backed by the C++ standard and here is the passage

14.7.1p1 ... The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions or default arguments, of the class member functions, member classes, scoped member enumerations, static dataembers and member templates; and it causes the implicit instantiation of the definitions of unscoped member enumerations and member anonymous unions.

and another relevant section found by @gx_

14.7.1p8 The implicit instantiation of a class template does not cause any static data members of that class to be implicitly instantiated.

A work around is the one mentioned by @gx_: Simply add

         ExportToLua() { (void)&m_registered; }

to the constructor. Taking the address forces the instantiation of the static variable m_registered.

chris.schuette
  • 307
  • 2
  • 11
  • 3
    Actually I just found (in N3337) another relevant quote, a bit farther in the same section: _14.7.1p8 The implicit instantiation of a class template does not cause any static data members of that class to be implicitly instantiated._ – gx_ Sep 02 '13 at 10:55
  • I think [temp.inst]/2 is the most appropriate ;) "Unless a member of a class template [...] has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist; **in particular, 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.**" – dyp Sep 02 '13 at 23:28
3

You already found the reason in the standard why the behavior is the way it is. So as a workaround, you can 'trick' the compiler into instantiating that static member by referencing it from either the template constructor or destructor.

#define FORCE_INSTANTIATE(x) (x)
// or (avoids -Wall and -pedantic warnings)
// template <typename T> inline void FORCE_INSTANTIATE(T) {}

template <class T>
class ExportToLua
{
  public:
    ExportToLua() {}
    virtual ~ExportToLua() { FORCE_INSTANTIATE(m_registered); }
  private:
      static int m_registered;
};

It seems to work in this demo.

Edit: As DyP correctly pointed out, the One-Defintion-Rule comes into play here in whether ExportToLua<T>::m_registered gets instantiated or not.

To guarantee implicit instantiation, make sure you meet at least one of the following conditions:

  • Provide a definition for either the constructor or destructor of the class that's to be exported.
  • You create an instance of that class that's used elsewhere in other parts of your code. This will force the compiler to provide a default ctor if you didn't provide one thereby triggering the necessary template instantiations.

If none of those conditions can be met for whatever reason then you'll need to explicitly instantiate the members you want from the template. For example,

class Example: public ExportToLua<Example>
{
public:
  // ...
  static int exportToLua();
  // etc.
};
template int ExportToLua<Example>::m_registered;

You can wrap that into a macro to make it nicer to use if desired.

greatwolf
  • 20,287
  • 13
  • 71
  • 105
  • I get no output from this demo using GCC 4.8.1 20130725 (prerelease) on an Archlinux x64 box, not even disabling optimizations, unless I explicitly try to read `m_registered`, so I don't think this is a reliable workaround. -1 while on hold for a better workaround. – brunocodutra Sep 02 '13 at 20:01
  • A much more reliable workaround is to force the construction of a global object of type `ExportToLua` whose default constructor should call `T::exportToLua()`. The constructor is required by the standard to be called. That can be achieved defining the macro `#define EXPORT_TO_LUA(X) ExportToLua _##X` and calling it whenever one feels one's class should be exported, like so: `EXPORT_TO_LUA(Example)`. Caveats: ExportToLua can't be inherited, but I see no reason for it to be either. EXPORT_TO_LUA must be called only once per class to be exported. – brunocodutra Sep 02 '13 at 20:24
  • @brunocodutra I've tested this on mingw g++ 4.6.3 and mingw g++ 4.8.0 and they both print the output on my machine. I've also tested it on msvc cl v16.00.30319 and that also outputs it correctly. Does changing `FORCE_INSTANTIATE` into a template function change anything for you? – greatwolf Sep 02 '13 at 21:16
  • @brunocodutra the whole idea was somehow make the compiler instantiate that static member automatically without adding extra support code elsewhere. Sure you can create global objects and that'll do the trick but at that point you might as well just have the macro explicitly instantiate that member instead. There's no guarantee the compiler will optimize and remove that unused global object out. – greatwolf Sep 02 '13 at 21:21
  • I am pretty sure the compiler is required by the standard to instantiate every global object declared. I don't have any trutsworthy references for this claim, nor the time to look for it right now, my apologies, but I can assure you I've used this before on different combinations of compilers and platforms. – brunocodutra Sep 02 '13 at 21:30
  • I tested your example once again and for some reason it was working. I realized you included definitions for a default constructor as well as a virtual destructor for the class `Example`. If you comment out these definitions, which are not required for the code to comply, the example stops working on my setup again. Writing FORCE_INSTANTIATION as a template function does not change the behavior. I thus maintain the opinion this is a very unreliable workaround and still favor mine. – brunocodutra Sep 02 '13 at 21:42
  • @brunocodutra I think you misunderstood my comment. I am not saying making global objects won't work -- of course it will. What I am saying is that it's a bit overkill and should be used only if there's no other resort. Here I am saying have the macro explicitly instantiate the static member instead so you avoid unnecessary globals. – greatwolf Sep 02 '13 at 21:43
  • @brunocodutra Just for my curiosity and testing, I just tried this on linuxlite in virtual box. Using g++ 4.6.3 it seems to work -- I really cannot reproduce the issue that you are seeing. :( – greatwolf Sep 02 '13 at 21:44
  • try commenting out lines 20 and 21 – brunocodutra Sep 02 '13 at 21:49
  • I see no way for you to "have the macro explicitly instantiate the static member" only once and at the program initialization if not by the use of global objects. I don't see any need for the static variable either. You seem to be concerned about space eficiency. Well as I see it, the compiler has a much harder time optimizing out a variable which actually stores the result of a function call, than a bogus object which has no data whatsoever and only exists for the purpose of calling a function which stores no data nowhere. – brunocodutra Sep 02 '13 at 21:58
  • @brunocodutra I mean something like this `#define EXPORT_TO_LUA(X) template int ExportToLua::m_registered;`. – greatwolf Sep 02 '13 at 22:13
  • I think you meant `#define EXPORT_TO_LUA(X) template<> int ExportToLua::m_registered = X::exportToLua()`, because as I understood what really matters is calling `exportToLua()` at startup, while `m_registered` is itself completely useless. You are right, that would work, but my comments about space eficiency still hold. Besides, why even inherit `ExportToLua`? Writing this or `FORCE_INSTANTIATION(Example)` looks exactly the same in terms of usability in my view. Either way, I suggest you update your answer to a more reliable workaround. – brunocodutra Sep 02 '13 at 22:27
  • @brunocodutra I meant what I said in the comment. To instantiate `ExportToLua::m_registered;` the compiler will use the general template form when generating code. In this case, that would be from the OP's original definition `template int ExportToLua::m_registered = T::exportToLua();`. In effect, it would be the same as if you had manually written `int ExportToLua_Example::m_registered = Example::exportToLua();`. I can't say for certain if the OP had other plans for `m_registered` or he's merely using it as a flag to indicate init status. – greatwolf Sep 02 '13 at 22:34
  • right, I was accounting for the previous initialization line to be removed. Maybe `m_registered` does have a purpose, in this case the best solution in my view would be exactly this last one, defining `#define EXPORT_TO_LUA(X) template<> int ExportToLua::m_registered;` and requiring `EXPORT_TO_LUA` to be called once for every class to be exported. Right now I can see no better way. I insist the current workaround does not work in my setup for every use case. Once your answer be updated I'll gladly give it +1. – brunocodutra Sep 02 '13 at 22:39
  • @brunocodutra Note that `template<> int ExportToLua::m_registered;` is doing *specialization* not explicit instantiation here. eg. doing `EXPORT_TO_LUA(foo); ExportToLua::m_registered;` it does not init it using `foo::exportToLua()`. – greatwolf Sep 02 '13 at 22:52
  • on your previous answer I believed you defended exactly the opposite. Anyways I tested and your last comment is right, but, no mater what, it does not change the behavior on my setup. I stand corrected, the only viable solution I see right now is to define `#define EXPORT_TO_LUA(X) template<> int ExportToLua::m_registered = X::exportToLua()` and require it to be called once for every class to be exported. I tested this solution and it works on my setup. If I'm still missunderstanding you in anyway please let me know. – brunocodutra Sep 02 '13 at 23:02
  • just to make it clear, the only thing I'm claiming here is that the solution you proposed on your answer is not *reliable*, for I can give you an example where it does not work and that is all it takes to make it *unreliable* (hence -1). All I'm proposing is an update on your answer to include in it the minimum requirements necessary to make it work across compilers/platforms, whatever that might be. – brunocodutra Sep 02 '13 at 23:13
  • 1
    [stmt.expr]/1: "*expression[opt]* `;`" (an *expression-statement*) is a discarded-value expression. Additionally the *id-expression* `m_registered` can only appear here in an unevaluated operand (as per [expr.prim.general]/13). Therefore, the ODR [basic.def.odr] doesn't require a definition to exist, and I doubt anything else does. Hence, the instantiation isn't enforced. The template function version is different (and should work AFAIK). – dyp Sep 02 '13 at 23:39
  • @DyP I thought the template function version would work too, but it does not when I test it here (for both gcc and clang). Strangely, it starts working if I explicitly declare a destructor for the class `Example`, even if empty. I believe this fact shouldn't alter the bahavior, since the parents class destructor is always required to be called and therefore the compiler is required to generate code for it, thus implying a definition of `m_registered` through `EXPORT_TO_LUA`, this way I can only conclude it is pure coincidence, a simple matter of choice made by the compiler. – brunocodutra Sep 03 '13 at 21:12
  • @brunocodutra it's pod-ness or aggregate status is affected by the presence of a user declared ctor or dtor. Though it's unclear to me how that interacts with implicit template instantiation. Probably worth asking as a separate question. – greatwolf Sep 03 '13 at 21:18
  • @brunocodutra The implicitly declared "default" dtor is not defined unless odr-used [class.dtor]/6. The problem of trying to odr-use the variable in a member function might that that it's a member function of a class template and therefore itself only instantiated when a definition is required. I.e. it could be that as the virtual dtor is not explicitly (odr-)used, all of the odr-uses within it "do not matter". – dyp Sep 03 '13 at 21:22
  • @DyP Exactly, then for instance unless an object of class `Example` is created, the class won't be exported (I tested and this consideration holds indeed). I'm not familiar with lua, but this might not be the intended behavior for the automatic mechanism of exporting classes to lua. If this be the case, then I insist the exporting routine should be somehow explicitly called. – brunocodutra Sep 03 '13 at 21:28
  • @brunocodutra As you said, an explicitly defined dtor (even if defaulted) in `Example` works, because it forces the definition of `ExportToLua::~ExportTuLua`, which itself then can force the initialization of `m_registered`. But this is error prone of course, as the code also compiles leaving out that explicit dtor. A non-default ctor on the other hand (preferably of a virtual base class) would be a bit less error prone (if the derived class is not a template itself); an explicit instantiation *definition* of this static member is safe (but opposed to the intended automation). – dyp Sep 03 '13 at 21:45
  • 1
    @DyP you pretty nailed it, so to sum it up, as such, this technic is guaranteed to work if the user guarantees either that at least one object of the class to be exported is created, or that it has an explicitly defined destructor. – brunocodutra Sep 03 '13 at 21:59
  • @brunocodutra "or that it [= the derived class] has an explicitly defined destructor" *and is not a template itself* (or to be more precise but less clear, if its dtor is required to be defined) – dyp Sep 03 '13 at 22:06
  • @DyP I've edit my answer, can you check if I missed anything important? – greatwolf Sep 03 '13 at 22:11
  • Wow I think we need a big comment cleanup session after this ;) @greatwolf I think it's a bit more subtle than just requiring the existence of an instance or an explicit dtor: If that instance is created e.g. in a function template or member function of a class template, it probably won't help if that function is not instantiated. If the explicit dtor is a member of a class template, it probably won't help either (if it's not instantiated). – dyp Sep 03 '13 at 22:57
  • @DyP good point, though for the OP's use case you can't exactly export the template itself in which case he'll have to specify exactly what instantiations he wants exported. Agreed with the big comment cleanup session lol – greatwolf Sep 03 '13 at 23:43