I have an attribute vector that can hold different types:
class base_attribute_vector; // no template args
template
class raw_attribute_ve
enum class Type
{
Int,
String,
// ...
Unknown
};
Type TypeFromString(const std::string& s)
{
if (s == "int") { return Type::Int; }
if (s == "string") { return Type::String; }
// ...
return Type::Unknown;
}
template <template <typename> class>
struct base_of;
template <template <typename> class C>
using base_of_t = typename base_of<C>::type;
And then the generic factory
template <template <typename> class C>
std::unique_ptr<base_of_t<C>> make_templated(const std::string& typeStr)
{
Type type = TypeFromString(typeStr);
static const std::map<Type, std::function<std::unique_ptr<base_of_t<C>>()>> factory{
{Type::Int, [] { return std::make_unique<C<int>>(); } },
{Type::String, [] { return std::make_unique<C<std::string>>(); } },
// ...
{Type::Unknown, [] { return nullptr; } }
};
return factory.at(type)();
}
a specialization is needed for each base:
template <>
struct base_of<raw_attribute_vector> {
using type = base_attribute_vector;
};
And then
auto p = make_templated<raw_attribute_vector>(s);
Demo
Short answer: no, you can't instruct the compiler to evaluate a runtime condition in compile time. Not even with hana.
Long answer: there are some (mostly language independent) patterns for this.
I'm assuming that your base_attribute_vector
has some virtual
method, most likely pure
, commonly called an interface
in other languages.
Which means that depending on the complexity of your real problem, you probably want a factory or an abstract factory.
You could create a factory or abstract factory without virtual methods in C++, and you could use hana for that. But the question is: is the added complexity really worth it for that (possibly really minor) performance gain?
(also if you want to eliminate every virtual call, even from base_attribute_vector
, you have to make everything using that class a template, after the entry point where the switch happens)
I mean, have you implemented this with virtual methods, and measured that the cost of the virtual calls is too significant?
Edit: another, but different solution could be using a variant type with visitors, like eggs::variant.
With variant
, you can create classes with functions for each parameter type, and the apply
method will switch which function to run based on it's runtime type.
Something like:
struct handler {
void operator()(TypeA const&) { ... }
void operator()(TypeB const&) { ... }
// ...
};
eggs::variant< ... > v;
eggs::variants::apply(handler{}, v);
You can even use templated operators (possibly with enable_if/sfinae), if they have common parts.
Largely based on Jarod42's answer, this is what I will be using:
class base_attribute_vector {};
template<typename T>
class raw_attribute_vector : public base_attribute_vector {
public:
raw_attribute_vector() {std::cout << typeid(T).name() << std::endl; }
};
template<class base, template <typename> class impl>
base* magic(std::string type) {
if(type == "int") return new impl<int>();
else if(type == "float") return new impl<float>();
}
int main() {
auto x = magic<base_attribute_vector, raw_attribute_vector>("int");
auto y = magic<base_attribute_vector, raw_attribute_vector>("float");
}
You cannot do this. At best, you need to support a limited number of types, and switch between them using an if
statement that can be evaluated at compile time.
I'd use an std::map
that has strings as key and std::function
as values. I would associate the string with a function that returns your type. Here's an example:
using functionType = std::function<std::unique_ptr<base_attribute_vector>()>;
std::map<std::string, functionType> theMap;
theMap.emplace("int", []{ return new raw_attribute_vector<int>; });
theMap.emplace("float", []{ return new raw_attribute_vector<float>; });
// Using the map
auto base_vec = theMap["int"](); // base_vec is an instance of raw_attribute_vector<int>
Of course, this solution is valid if you only know the string value at runtime.
I'd probably do something like this:
Features:
1 - time registration of objects by passing a named prototype
constant time lookup at runtime
lookup by any type which can be compared to std::string
-
#include <unordered_map>
#include <string>
struct base_attribute_vector { virtual ~base_attribute_vector() = default; };
template<class Type> struct attribute_vector : base_attribute_vector {};
// copyable singleton makes handling a breeze
struct vector_factory
{
using ptr_type = std::unique_ptr<base_attribute_vector>;
template<class T>
vector_factory add(std::string name, T)
{
get_impl()._generators.emplace(std::move(name),
[]() -> ptr_type
{
return std::make_unique< attribute_vector<T> >();
});
return *this;
}
template<class StringLike>
ptr_type create(StringLike&& s) const {
return get_impl()._generators.at(s)();
}
private:
using generator_type = std::function<ptr_type()>;
struct impl
{
std::unordered_map<std::string, generator_type, std::hash<std::string>, std::equal_to<>> _generators;
};
private:
static impl& get_impl() {
static impl _ {};
return _;
}
};
// one-time registration
static const auto factory =
vector_factory()
.add("int", int())
.add("double", double())
.add("string", std::string());
int main()
{
auto v = factory.create("int");
auto is = vector_factory().create("int");
auto strs = vector_factory().create("string");
}