template<class T>
struct named_factory {
const char* name;
std::function<std::unique_ptr<T>()> factory;
};
struct find_factory {
using is_transparent=std::true_type;
struct named {
const char* str;
template<class T>
named(named_factory<T> const& f):str(f.name) {}
named(const char* name):str(name) {}
};
bool operator()(named lhs, named rhs) {
return strcmp(lhs.str, rhs.str)<0;
}
};
#define MAKE_STR2(X) #X
#define MAKE_STR(X) MAKE_STR2(X)
#define FACTORY(X,...) \
named_factory<__VA_ARGS__>{\
MAKE_STR(X),\
[]{\
return std::make_unique<X>()\
}\
}
Now we can:
std::set<named_factory<foo>, find_factory> factories = {
FACTORY(P2_26, foo),
FACTORY(P4_30, foo),
// ...
};
and in code you do:
bool add_module_by_name( const char* name ) {
auto it = factories.find(name);
if (it == factories.end()) return false;
auto module = it->factory();
if (!module) return false;
add_module( module.release() );
return true;
}
This is a data-driven design. The search for the right type is done in logarithmic time, not linear like your code. You could probably replace it with an unordered_map
instead of a set
.
However, if your type names are determined at compile time, you can do better. (Ie, if you have a hard coded "P2_26"
at the call site).
template<class T>
struct tag_t { using type=T; constexpr tag_t(){} };
template<class T>
constexpr tag_t<T> tag{};
template<class T>
void add_module( tag_t<T> ) {
// ...
add_module( new T() );
}
Now you can add_module(tag<P2_26>)
and skip the long if/else statement.
We can even hide the implementation of the outer add_module
via this:
// in cpp file:
void add_module_impl( std::function< std::unique_ptr<foo>() > maker ) {
// ...
add_module( maker().release() );
}
// in h file:
void add_module_impl( std::function< std::unique_ptr<foo>() > maker );
template<class T>
void add_module( tag_t<T> t ) {
add_module_impl([]{ return std::make_unique<T>(); });
}
and again, we can add_module(tag<P4_30>)
and it just works.