[英]Choose template based on run-time string in C++
I have an attribute vector that can hold different types: 我有一个属性向量,可以包含不同的类型:
class base_attribute_vector; // no template args
template<typename T>
class raw_attribute_vector : public base_attribute_vector;
raw_attribute_vector<int> foo;
raw_attribute_vector<std::string> foo;
Based on run-time input for the type, I would like to create the appropriate data structure. 根据类型的运行时输入,我想创建适当的数据结构。 Pseudocode:
伪代码:
std::string type("int");
raw_attribute_vector<type> foo;
Obviously, this fails. 显然,这失败了。 An easy, but ugly and unmaintainable workaround is a run-time switch/chained if:
如果出现以下情况,一个简单但丑陋且难以维护的解决方法是运行时切换/链接:
base_attribute_vector *foo;
if(type == "int") foo = new raw_attribute_vector<int>;
else if(type == "string") ...
I read about run-time polymorphism with functors, but found it quite complex for a task that is conceptually easy. 我读到了有关仿函数的运行时多态性,但发现它对于概念上容易的任务来说非常复杂。
What is the best and cleanest way to make this work? 使这项工作最好,最干净的方法是什么? I played around with
boost::hana
, finding that while I can create a mapping from string to type, the lookup can only be done at compile time: 我玩了
boost::hana
,发现虽然我可以创建从字符串到类型的映射,但查找只能在编译时完成:
auto types =
hana::make_map(
hana::make_pair(BOOST_HANA_STRING("int"), hana::type_c<int>),
hana::make_pair(BOOST_HANA_STRING("string"), hana::type_c<std::string>)
);
All possible types are known at compile-time. 所有可能的类型在编译时都是已知的。 Any suggestions are highly appreciated.
任何建议都非常感谢。 In a perfect solution, I would create the name->type mapping in a single place.
在一个完美的解决方案中,我将在一个地方创建名称 - >类型映射。 Afterwards, I would use it like this
之后,我会像这样使用它
std::vector<base_attribute_vector*> foo;
foo.push_back(magic::make_templated<raw_attribute_vector, "int">);
foo.push_back(magic::make_templated<raw_attribute_vector, "std::string">);
foo[0]->insert(123);
foo[1]->insert("bla");
foo[0]->print();
foo[1]->print();
It is not required for this magic to happen at compile time. 这种魔法不需要在编译时发生。 My goal is to have as readable code as possible.
我的目标是拥有尽可能可读的代码。
I'd use an std::map
that has strings as key and std::function
as values. 我使用
std::map
,其中字符串作为键, std::function
作为值。 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. 当然,如果您只在运行时知道字符串值,则此解决方案有效。
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);
I'd probably do something like this: 我可能会这样做:
Features: 特征:
1 - time registration of objects by passing a named prototype 1 - 通过传递命名原型来注册对象
constant time lookup at runtime 运行时的常量时间查找
lookup by any type which can be compared to std::string
可以与
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");
}
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. 最好的情况是,您需要支持有限数量的类型,并使用可在编译时评估的
if
语句在它们之间切换。
Short answer: no, you can't instruct the compiler to evaluate a runtime condition in compile time. 简短回答:不,你不能指示编译器在编译时评估运行时条件。 Not even with hana.
甚至没有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. 我假设你的
base_attribute_vector
有一些virtual
方法,很可能是pure
,通常称为其他语言的interface
。
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. 你可以在C ++中创建一个没有虚方法的工厂或抽象工厂,你可以使用hana。 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) (如果你想消除每一个虚拟调用,即使是从
base_attribute_vector
,你必须在切换发生的入口点之后使用该类作为模板的所有内容 )
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 . 编辑:另一个,但不同的解决方案可能是使用带访问者的变体类型,如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. 使用
variant
,您可以为每个参数类型创建具有函数的类, apply
方法将根据其运行时类型切换要运行的函数。
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. 如果它们具有公共部分,您甚至可以使用模板化运算符(可能使用enable_if / sfinae)。
Largely based on Jarod42's answer, this is what I will be using: 主要基于Jarod42的答案,这就是我将要使用的内容:
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");
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.