Home > Software engineering >  Choose C template at runtime
Choose C template at runtime

Time:12-06

Is there any way to achieve the functionality of below code without creating the mapping between strings and classes manually?

template<class base, typename T>
base* f(const std::string &type, T &c) {

    if(type == "ClassA") return new ClassA(c);
    else if(type == "ClassB") return new ClassB(c);
    // many more else if...

    return nullptr;
}

All classes looks something like this:

class ClassA: public BaseClass {
public:
    std::string label="ClassA";
    ...

};

And we can use it as:

BaseClass *b = f<BaseClass>("ClassA", DifferentObject);

Each new class results in a new if else line of code. Is there any way to automate this so f function "updates" itself when new supported class is added? The solution must work for C 11.

CodePudding user response:

A possible macro:

#include <memory>
#include <string>

class BaseClass {};
class ClassA : public BaseClass {
  public:
    std::string label = "ClassA";
    explicit ClassA(int /*unused*/) {}
};
class ClassB : public BaseClass {
  public:
    std::string label = "ClassB";
    explicit ClassB(int /*unused*/) {}
};
template<class base, typename T>
auto f(const std::string &type, T c) -> std::unique_ptr<base> {
#define CASE(NAME)                                                                                                     \
    if (type == "NAME") {                                                                                              \
        return std::unique_ptr<base>(new NAME(c));                                                                     \
    }
    CASE(ClassA)
    CASE(ClassB)
//...
#undef CASE
    return nullptr;  // Statement at the end needed for last else!
}
auto main() -> int {
    auto b = f<BaseClass>("ClassA", 0);
}

Also use unique_ptr since memory managing raw pointers are EVIL.

CodePudding user response:

In that case if the class name is equal to the string, you can simplify your code with the following macro:

#define STRING_TO_CLASS (className) if(type == "className") return new className(c);

template<class base, typename T>
base* f(const std::string &type, T &c) {

    STRING_TO_CLASS(ClassA)
    STRING_TO_CLASS(ClassB)

    return nullptr;
}

Personally I hate macros, but it disturbs only me. However, at compile time, the following code wil be generated, after the macros are resolved.

template<class base, typename T>
base* f(const std::string &type, T &c) {

    if(type == "ClassA") return new ClassA(c);
    if(type == "ClassB") return new ClassB(c);

    return nullptr;
}

As you see, in the end only the else keyword is removed. Also, you need to modify your code if a new class is added.

CodePudding user response:

You could use the registry pattern like this:

#include <map>
#include <functional>
#include <string>

template< typename T, typename X >
using Factory = std::function< T* ( X& ) >;


template< typename Base, typename X >
struct Registry {

    using Map = std::map<std::string,Factory<Base,X> >;
    static Map registry;

    template< typename T >
    struct Register {
        Register( const std::string& name ) {
            registry[ name ] = []( X& x ) -> T* { return new T(x); };
        }
    };
};

template< typename Base, typename X >
Base* factory(const std::string &type, X &c ) {
    auto it = Registry<Base,X>::registry.find( type );
    if ( it!=Registry<Base,X>::registry.end() ) {
        return (it->second)(c);
    }
    return nullptr;
}

struct X {};

struct A {
    A( X& x ) {};
    virtual ~A() {}
};

struct B : public A {
    B( X& x ) : A(x) {};
};

struct C : public A {
    C( X& x ) : A(x) {};
};

struct D : public B {
    D( X& x ) : B(x) {};
};



// Register class
template<> Registry<A,X>::Map Registry<A,X>::registry{};
Registry<A,X>::Register<B> regB( "B" );
Registry<A,X>::Register<C> regC( "C" );
Registry<A,X>::Register<D> regD( "D" );

#include <iostream>
int main() {
    X x;
    A* ptr = factory<A,X>( "B", x );
    B* bptr = dynamic_cast<B*>( ptr );
    if ( bptr!= nullptr ) {
        std::cout << "Success!" << std::endl;
        return 0;
    }
    std::cout << "Failed!" << std::endl;
    return 1;
}
  • Related