C++中根据运行-时间字符串选择模板
Choose template based on run-time string in C++
我有一个可以容纳不同类型的属性向量:
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;
基于 运行 时间输入的类型,我想创建适当的数据结构。伪代码:
std::string type("int");
raw_attribute_vector<type> foo;
显然,这失败了。一个简单但丑陋且无法维护的解决方法是 运行 次 switch/chained 如果:
base_attribute_vector *foo;
if(type == "int") foo = new raw_attribute_vector<int>;
else if(type == "string") ...
我阅读了有关函子的 运行 时间多态性,但发现它对于概念上很容易的任务来说相当复杂。
完成这项工作的最佳和最干净的方法是什么?我尝试了 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>)
);
所有可能的类型在编译时都是已知的。任何建议都非常感谢。在一个完美的解决方案中,我会在一个地方创建 name->type 映射。之后,我会这样使用它
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();
这个魔法不需要在编译时发生。我的目标是拥有尽可能可读的代码。
简短回答:不,您不能指示编译器在编译时评估 运行时间条件。连hana都不行。
长答案:有一些(主要是与语言无关的)模式。
我假设您的 base_attribute_vector
有一些 virtual
方法,很可能是 pure
,在其他语言中通常称为 interface
。
这意味着根据您实际问题的复杂程度,您可能需要 factory or an abstract factory.
您可以在 C++ 中创建一个没有虚拟方法的工厂或抽象工厂,您可以为此使用 hana。但问题是:为了获得(可能非常小的)性能提升,增加的复杂性真的值得吗?
(另外,如果你想消除每个虚拟调用,即使是来自 base_attribute_vector
,你必须使用 class 模板制作 everything,之后发生切换的入口点)
我的意思是,你有没有用虚拟方法实现这个,并且测量到虚拟调用的成本太大了?
编辑:另一个但不同的解决方案可能是对访问者使用变体类型,例如 eggs::variant。
使用 variant
,您可以为每个参数类型创建带有函数的 classes,apply
方法将根据它的 运行时间类型.
类似于:
struct handler {
void operator()(TypeA const&) { ... }
void operator()(TypeB const&) { ... }
// ...
};
eggs::variant< ... > v;
eggs::variants::apply(handler{}, v);
您甚至可以使用模板化运算符(可能与 enable_if/sfinae 一起使用),如果它们有共同的部分。
我会使用一个 std::map
将字符串作为键,将 std::function
作为值。我会将字符串与 returns 您的类型的函数相关联。这是一个例子:
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>
当然,如果你只知道运行时的字符串值,这个解决方案是有效的。
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;
然后是通用工厂
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)();
}
每个基地都需要专业化:
template <>
struct base_of<raw_attribute_vector> {
using type = base_attribute_vector;
};
然后
auto p = make_templated<raw_attribute_vector>(s);
我可能会这样做:
特点:
1 - 通过命名原型
对象的时间注册
在运行时进行恒定时间查找
通过可以与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");
}
主要基于 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");
}
我有一个可以容纳不同类型的属性向量:
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;
基于 运行 时间输入的类型,我想创建适当的数据结构。伪代码:
std::string type("int");
raw_attribute_vector<type> foo;
显然,这失败了。一个简单但丑陋且无法维护的解决方法是 运行 次 switch/chained 如果:
base_attribute_vector *foo;
if(type == "int") foo = new raw_attribute_vector<int>;
else if(type == "string") ...
我阅读了有关函子的 运行 时间多态性,但发现它对于概念上很容易的任务来说相当复杂。
完成这项工作的最佳和最干净的方法是什么?我尝试了 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>)
);
所有可能的类型在编译时都是已知的。任何建议都非常感谢。在一个完美的解决方案中,我会在一个地方创建 name->type 映射。之后,我会这样使用它
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();
这个魔法不需要在编译时发生。我的目标是拥有尽可能可读的代码。
简短回答:不,您不能指示编译器在编译时评估 运行时间条件。连hana都不行。
长答案:有一些(主要是与语言无关的)模式。
我假设您的 base_attribute_vector
有一些 virtual
方法,很可能是 pure
,在其他语言中通常称为 interface
。
这意味着根据您实际问题的复杂程度,您可能需要 factory or an abstract factory.
您可以在 C++ 中创建一个没有虚拟方法的工厂或抽象工厂,您可以为此使用 hana。但问题是:为了获得(可能非常小的)性能提升,增加的复杂性真的值得吗?
(另外,如果你想消除每个虚拟调用,即使是来自 base_attribute_vector
,你必须使用 class 模板制作 everything,之后发生切换的入口点)
我的意思是,你有没有用虚拟方法实现这个,并且测量到虚拟调用的成本太大了?
编辑:另一个但不同的解决方案可能是对访问者使用变体类型,例如 eggs::variant。
使用 variant
,您可以为每个参数类型创建带有函数的 classes,apply
方法将根据它的 运行时间类型.
类似于:
struct handler {
void operator()(TypeA const&) { ... }
void operator()(TypeB const&) { ... }
// ...
};
eggs::variant< ... > v;
eggs::variants::apply(handler{}, v);
您甚至可以使用模板化运算符(可能与 enable_if/sfinae 一起使用),如果它们有共同的部分。
我会使用一个 std::map
将字符串作为键,将 std::function
作为值。我会将字符串与 returns 您的类型的函数相关联。这是一个例子:
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>
当然,如果你只知道运行时的字符串值,这个解决方案是有效的。
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;
然后是通用工厂
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)();
}
每个基地都需要专业化:
template <>
struct base_of<raw_attribute_vector> {
using type = base_attribute_vector;
};
然后
auto p = make_templated<raw_attribute_vector>(s);
我可能会这样做:
特点:
1 - 通过命名原型
对象的时间注册
在运行时进行恒定时间查找
通过可以与
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");
}
主要基于 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");
}