4

So, lets start with the code that does what I want, but I'd like to teach it a new trick (if it is possible).

#include <string>
#include <iostream>

class A {}; class B {}; class C {}; class D {};

template<typename T> struct traits { };
template<> struct traits<A> { static std::string code() {return "class A";}};
template<> struct traits<B> { static std::string code() {return "class B";}};
template<> struct traits<C> { static std::string code() {return "class C";}};
template<> struct traits<D> { static std::string code() {return "class D";}};

std::string RegisterClass(const std::string & c){
    std::cout << "Registering " << c << '\n';
    return c;
}

template<typename T> void printMe() {
    static std::string t = RegisterClass(traits<T>::code());
    std::cout << "Printing " << traits<T>::code() << '\n';
}

int main(void)
{
    printMe<B>(); 
    printMe<B>(); 
    printMe<C>(); 
    return 0;
}

The output of it is as one might expect -- the "registration" happens only once and only for those types that the template was instantiated with:

Registering class B
Printing class B   
Printing class B   
Registering class C
Printing class C

What I would like to have is to have only the classes that was used to "register" themselves before the first call to the printMe function. So the output would look like this:

Registering class B
Registering class C
Printing class B   
Printing class B   
Printing class C

Superficially, that seems possible. The compiler "knows" which types was used for instantiation . If I were able to store that information in some global or static thingy, then I'd just process it at the beginning of main().

But all my attempts to subvert the compiler into actually doing it have failed so far. Which made me suspect that it is intentional. So 'Can I have template instantiation side-effects? ' he asked, expecting the answer 'no'?

Edit: I'm sorry, but I've really failed to clearly express myself in that sentence. In the example above I don't need the registration for A,B,C and D -- I need it only for B and C and I want the compiler to figure that out by himself somehow.

Kostya
  • 1,072
  • 11
  • 24
  • Could you afford using a macro/preprocessor ? – Tasos Vogiatzoglou Jan 20 '15 at 19:08
  • @oglu, obviously, I'd love a pure soution. But If you have something that is industrial-solid standed-the-test-of-time I'd consider that. – Kostya Jan 20 '15 at 20:04
  • The industrial-solid standed-the-test-of-time solution is to use a REGISTER macro that does the registration, like what most of the test frameworks do. But, there is this guy that has a solution (albeit on the undefined behavior side) which works as you'd like http://stackoverflow.com/questions/9975672/c-automatic-factory-registration-of-derived-types (check the last answer) – Tasos Vogiatzoglou Jan 20 '15 at 20:34

5 Answers5

3

Yes, using a static member variable that is instantiated in namespace scope. For example:

template<typename T>
struct RegisterClass
{
    static RegisterClass instance;
    RegisterClass()
    {
        std::cout << "Registering " << traits<T>::code() << '\n';
    }
    static RegisterClass& doRegister() { return instance; }
};
template<typename T>
RegisterClass<T> RegisterClass<T>::instance;

template<typename T> void printMe() {
    RegisterClass<T>::doRegister();
    std::cout << "Printing " << traits<T>::code() << '\n';
}

Working example.

The only reason to use a static member function doRegister is to avoid warnings from unused variables.

See also How does the scheme below garantees there'll be just one definition for the objects cin, cout, ...? for some wrinkles related to global objects and order of initialization.

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • What if someone accesses `instance` before it's constructed? Typically the point of static locals is to prevent order-of-initialization bugs. – Mark B Jan 20 '15 at 20:08
  • @MarkB `instance` can be private. – ecatmur Jan 20 '15 at 20:12
  • @MarkB it's still entirely possible to have order-of-initialization bugs in static objects and indeed many people incorrectly abuse static objects in threaded code – inetknght Jan 20 '15 at 20:24
1

You can do that by creating a helper class whose constructor takes care of registering the classes. Then create an instance of the helper class in external scope.

#include <string>
#include <iostream>

class A {}; class B {}; class C {}; class D {};

template<typename T> struct traits { };
template<> struct traits<A> { static std::string code() {return "class A";}};
template<> struct traits<B> { static std::string code() {return "class B";}};
template<> struct traits<C> { static std::string code() {return "class C";}};
template<> struct traits<D> { static std::string code() {return "class D";}};

template<typename T> 
void registerClass(){
    std::cout << "Registering " << traits<T>::code() << '\n';
}

template<typename T> void printMe() {
    std::cout << "Printing " << traits<T>::code() << '\n';
}

struct Init
{
   Init();
};

Init::Init()
{
   registerClass<A>();
   registerClass<B>();
   registerClass<C>();
   registerClass<D>();
}

static Init init;

int main(void)
{
    printMe<B>(); 
    printMe<B>(); 
    printMe<C>(); 
    return 0;
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • This solves the problem but doesn't answer the (possibly academic only, but interesting in my opinion) actual question. – Mark B Jan 20 '15 at 19:20
  • Similar idea as my answer. However this would require adding every type of `T` to the `Init`. On the plus side, it would mean that he could *selectively* have side effects of instantiating each `T` rather than automatically having side effects. – inetknght Jan 20 '15 at 19:21
  • @RSahu I'm really sorry that I've confused you -- see my edit. (Still, you might notice that the output of your code doesn't correspond to the desired one.) – Kostya Jan 20 '15 at 19:27
1

Add a static member variable and instantiate it.

namespace {
    template<typename T>
    class registrar {
    public:
        registrar() {
            std::string t = RegisterClass(traits<T>::code());
        }
    };
}
template <class type> struct traits {
    virtual std::string code() = 0; // override this
private:
    static registrar m_registrar;
};
registrar traits<A>::m_registrar;

With this way, any given T for traits<T> will therefore have a side effect of calling registrar<T>::registrar() at runtime start.

inetknght
  • 4,300
  • 1
  • 26
  • 52
0

This works, but is a bad answer because I'm not sure why the static variable are so weird.

#include <iostream>
using namespace std;

template <typename T>
class Registrar {
public:
    Registrar();
};

template <typename D>
struct SelfRegistrar {
   static Registrar<D> d_;
    SelfRegistrar(){
        cout << &d_ << endl;
    }
};



//for some reason I don't understand need some reference to d, opening another question...
class A : SelfRegistrar<A> { static void* foo() {return &d_;} };
class B : SelfRegistrar<B> { static void* foo() {return &d_;}};
class C : SelfRegistrar<C> { static void* foo() {return &d_;}};
class D : SelfRegistrar<D> { static void* foo() {return &d_;}};

template<typename T> struct traits { };
template<> struct traits<A> { static std::string code() {return "class A";}};
template<> struct traits<B> { static std::string code() {return "class B";}};
template<> struct traits<C> { static std::string code() {return "class C";}};
template<> struct traits<D> { static std::string code() {return "class D";}};

template <typename D>
Registrar<D> SelfRegistrar<D>::d_{};

std::string RegisterClass(const std::string & c){
    std::cout << "Registering " << c << '\n';
    return c;
}

template<typename T> void printMe() {
    std::cout << "Printing " << traits<T>::code() << '\n';
}

template <typename D>
Registrar<D>::Registrar()
{
   std::string c = traits<D>::code();
   std::cout << "Registering " << c << '\n';
}

int main() {
    // your code goes here
    printMe<B>(); 
    printMe<B>(); 
    printMe<C>(); 
    return 0;
}
IdeaHat
  • 7,641
  • 1
  • 22
  • 53
0

Sometimes it's more convenient to derive from a base class rather than use embedded or additional helper classes for things like this. Depending on your needs, this approach can sometimes make things a bit simpler/cleaner and more reusable.

For example:

#include <string>
#include <iostream>
#include <typeinfo>

// Forward declaration for "traits" wrapper class.  If you wanted to, you 
// could to change the self_registering template to not use a wrapper class, 
// default a wrapper but allow other specializations, etc.
template<typename T> struct traits;

template<typename wrappedClass>
struct self_registering {
    self_registering() {};
    virtual ~self_registering() {};
    static bool isRegistered() { return registered; }

private:
    typedef traits<wrappedClass> WrappedClass_;
    static bool doRegistration() {
        std::cout << "Default-registering " << WrappedClass_().code() << '\n';
        return true;
    };
    static const bool registered;
};
template<typename T>
const bool self_registering<T>::registered 
         = self_registering<T>::WrappedClass_().doRegistration();

// our traits wrapper class...
template<typename T> 
struct traits : self_registering<T>   {
    static std::string code() { return typeid(T).name(); };
};

// Well, that's pretty much it. Time to use it:

// a few classes we're going to (maybe) want self-registered...
class A {}; class B {}; class C {}; class D {};
class E {}; class F {}; class G {}; class H {};

// provide custom registration for class H:
template<> struct traits<H> : self_registering<H> { 
    static std::string code() { return "class H"; }

    bool doRegistration() {
        std::cout << "Private-registering " << code() << "\n";
        // do something here like H::register()...
        return true;
    };
};

template<typename T>
void printMe() {
    static bool isRegistered = traits<T>::isRegistered();
    (void)isRegistered; // shut gcc up about unused variable
    std::cout << "Printing " << traits<T>::code() << '\n';
}

int main(void)
{
    printMe<B>(); 
    printMe<B>(); 
    printMe<C>(); 

    printMe<H>(); 

    return 0;
}

With a pure template-based approach for self-registration, one drawback is that if all of your class members functions are static, you will at some point need to somehow force registration (as seen in other answers - e.g. RegisterClass(), etc.).

The above code does this by simply referencing the value of a static member variable registered in printMe(), forcing the compiler to initialize it (which it does by calling registration()). The value is assigned to a static bool, so registration should occur only once for each class at startup.

Although the order of registration is not guaranteed across translation units, it should be thread-safe under C++11 (and, if your code doesn't go multi-threaded before static objects are initialized, pre-C++11). As noted, the drawback is that the registered member variable must be referenced somewhere in your code.

Compiled/tested using VS2012 as:

C:\tmp>cl /nologo /EHsc  /W4 so-misc.cpp
so-misc.cpp

C:\tmp>so-misc
Default-registering class B
Default-registering class C
Private-registering class H
Printing class B
Printing class B
Printing class C
Printing class H

and gcc 4.7.2 as:

$ gcc -std=c++11 -lstdc++ -Wall -pedantic so-misc.cpp
$ ./a.out | c++filt -t
Default-registering B
Default-registering C
Private-registering class H
Printing B
Printing B
Printing C
Printing class H

Obviously, there are other things you could do (further template-ize self-registering to make the wrapper class more generic than traits, some cleanup, etc.), but this might work for you as a start.

frasnian
  • 1,973
  • 1
  • 14
  • 24