我的一个项目中有很多自定义数据类型,它们都共享一个公共(public)基类。
我的数据(来自数据库)有一个数据类型,它由基类的枚举来区分。我的架构允许特定数据类型专门用于派生类,或者它可以由基类处理。
当我构造一个我的特定数据类型时,我通常直接调用构造函数:
Special_Type_X a = Special_Type_X("34.34:fdfh-78");
a.getFoo();
有一些模板魔术也允许像这样构造它:
Type_Helper<Base_Type::special_type_x>::Type a = Base_Type::construct<Base_Type::special_type_x>("34.34:fdfh-78");
a.getFoo();
对于枚举类型的某些值,可能没有专门化,所以
Type_Helper<Base_Type::non_specialized_type_1>::Type == Base_Type
当我从数据库中获取数据时,数据类型在编译时是未知的,所以有第三种构造数据类型的方法(从 QVariant):
Base_Type a = Base_Type::construct(Base_type::whatever,"12.23@34io{3,3}");
但是我当然希望调用正确的构造函数,所以该方法的实现过去看起来像:
switch(t) {
case Base_Type::special_type_x:
return Base_Type::construct<Base_Type::special_type_x>(var);
case Base_Type::non_specialized_type_1:
return Base_Type::construct<Base_Type::non_specialized_type_1>(var);
case Base_Type::whatever:
return Base_Type::construct<Base_Type::whatever>(var);
//.....
}
这段代码是重复的,因为基类也可以处理新类型(添加到枚举中),所以我想出了以下解决方案:
// Helper Template Method
template <Base_Type::type_enum bt_itr>
Base_Type construct_switch(const Base_Type::type_enum& bt, const QVariant& v)
{
if(bt_itr==bt)
return Base_Type::construct<bt_itr>(v);
return construct_switch<(Base_Type::type_enum)(bt_itr+1)>(bt,v);
}
// Specialization for the last available (dummy type): num_types
template <>
Base_Type construct_switch<Base_Type::num_types>(const Base_Type::type_enum& bt, const QVariant&)
{
qWarning() << "Type" << bt << "could not be constructed";
return Base_Type(); // Creates an invalid Custom Type
}
我原来的 switch 语句被替换为:
return construct_switch<(Base_Type::type_enum)0>(t,var);
此解决方案按预期工作。
但是编译后的代码是不同的。虽然原始 switch 语句的复杂度为 O(1),但新方法的复杂度为 O(n)。生成的代码递归调用我的辅助方法,直到找到正确的条目。
为什么编译器不能正确优化它?有没有更好的方法来解决这个问题?
类似问题: Replacing switch statements when interfacing between templated and non-templated code
最佳答案
这就是我所说的神奇开关问题——如何获取一个(范围的)运行时值并将其转换为编译时常量。
抽象地说,您想要生成此 switch 语句:
switch(n) {
(case I from 0 to n-1: /* use I as a constant */)...
}
您可以使用参数包生成类似于 C++ 中的代码。
我将从 c++14 开始-替换样板:
template<unsigned...> struct indexes {typedef indexes type;};
template<unsigned max, unsigned... is> struct make_indexes: make_indexes<max-1, max-1, is...> {};
template<unsigned... is> struct make_indexes<0, is...>:indexes<is...> {};
template<unsigned max> using make_indexes_t = typename make_indexes<max>::type;
现在我们可以轻松地创建从 0 到 n-1 的无符号整数的编译时序列。 make_indexes_t<50>
扩展为 indexes<0,1,2,3,
... ,48, 49>
. c++14版本在 O(1) 步中这样做,因为大多数(所有?)编译器实现 std::make_index_sequence
与一个内在的。上面以线性(在编译时——在运行时什么都不做)递归深度和二次编译时内存来做。这很糟糕,你可以做得更好(对数深度,线性内存),但是你有超过几百种类型吗?如果没有,这就足够了。
接下来,我们构建一个回调数组。因为我讨厌 C 遗留函数指针语法,所以我将放入一些毫无意义的样板文件来隐藏它:
template<typename T> using type = T; // pointless boilerplate that hides C style function syntax
template<unsigned... Is>
Base_Type construct_runtime_helper( indexes<Is...>, Base_Type::type_enum e, QVariant const& v ) {
// array of pointers to functions: (note static, so created once)
static type< Base_Type(const QVariant&) >* const constructor_array[] = {
(&Base_Type::construct<Is>)...
};
// find the eth entry, and call it:
return constructor_array[ unsigned(e) ](v);
}
Base_Type construct_runtime_helper( Base_Type::type_enum e, QVariant const& v ) {
return construct_runtime_helper( make_indexes_t< Base_Type::num_types >(), e, v );
}
Bob 是你的叔叔1。用于调度的 O(1) 数组查找(具有 O(n) 设置,理论上可以在您的可执行文件启动之前完成)。
1 “Bob's your Uncle”是英联邦的一句俗语,大致意思是“一切都完成了,一切正常”。
关于c++ - 优化switch的模板替换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23430249/