Search code examples
c++templatescompilationvariadic-templates

Object generation from different id types slow compilation


I have a of templated class that can generate an object instance from an ID. The context is networking code with object replication.

The code below shows a way that I can manage to do this, but it has the drawback of beeing very slow in compilation.

Does anyone know a "better" way to achieve what my example shows. I'm not sure how to make this question more clear, I hope the code speaks for itself.

I have looked at extern templates, but I do not see how to apply that to templated functions in templated classes. If anyone knows how to do that, that would solve the issue.

Alternatively a way to fix the ambiguous problem of MyRegistersSimple would also be greatly helpfull!

template<typename ID, typename Base>
class Register
{
public:
    void create(ID id) { m_types.at(id).second(); }
private:
    std::map<ID, std::function<std::unique_ptr<Base>(ID)>> m_types;
};

template<typename tag>
struct ID { int value; };

class ABase {};
class BBase {};
class CBase {};
using ID_A = ID<struct ID_A_TAG>;
using ID_B = ID<struct ID_B_TAG>;
using ID_C = ID<struct ID_C_TAG>;

class MyRegistersSimple :
    public Register<ID_A, ABase>,
    public Register<ID_B, BBase>,
    public Register<ID_C, CBase>
{
};

template<typename... Registers>
class MultiRegister : public Registers...
{
public:
    template<typename ID>
    void create(ID)
    {
        // lots of complex template code to find the correct Register from 'Registers...'
        // and call 'create' on it
        // this makes compilation very slow
    }
};

class MyRegistersComplex : public MultiRegister<
    Register<ID_A, ABase>,
    Register<ID_B, BBase>,
    Register<ID_C, CBase>>
{};

void test()
{
    MyRegistersSimple simple;
    simple.create(ID_A(0)); // -> ambiguous, doest not compile

    MyRegistersComplex complex;
    complex.create(ID_A(0)); // -> very slow compilation
}

Solution

  • Basic solution

    Bring all the bases into scope via using:

    // a helper to avoid copy pasting `using`s
    template<typename... Registers> struct MultiRegister : Registers... { using Registers::create...; };
    
    class MyRegisters : public MultiRegister<
        Register<ID_A, ABase>,
        Register<ID_B, BBase>,
        Register<ID_C, CBase>>
    {};
    
    void test() {
        MyRegisters registers;
        registers.create(ID_A(0)); // IDE shows that `Register<ID<ID_A_TAG>, ABase>` is chosen
    }
    

    I hope the built-in overload resolution is faster than "lots of complex template code" in the ...Complex version.

    Offtopic improvement

    I didn't like that manual Register<ID_A, ABase> ID_x <-> xBase dispatch and dummy ID_x_TAGs, so I removed all of that (if using xBase as the ID's template parameter is fine). Then Register<ID_A, ABase>, Register<ID_B, BBase> etc. become

    template<typename Base> using MakeRegister = Register<ID<Base>, Base>;
    

    and the code suggested above is just (test() omitted - it's the same)

    template<typename... Registers> struct MultiRegister : Registers... { using Registers::create...; };
    template<typename... Bases> using MakeMultiRegister = MultiRegister<MakeRegister<Bases>...>;
    class MyRegisters : public MakeMultiRegister<ABase, BBase, CBase> {};