考虑以下代码
#include <iostream>
enum MyEnum{
A,
B,
END
};
template <int N>
class Trait {};
template<>
class Trait<A> {
public:
static int funct(int i) {return i*3;}
};
template<>
class Trait<B> {
public:
static int funct(int i) {return i*24;}
};
using namespace std;
int main(){
int i = 1;
switch(i){
case A: cout << Trait<A>::funct(i) << endl; break;
case B: cout << Trait<B>::funct(i) << endl; break;
}
}
将在屏幕上打印24个。
现在假设我在枚举中有更多的值,并且我定义了所有相应的值 Trait类的模板特化。
为了避免编写switch语句中所需的所有代码,我编写了一个几乎可以工作的REPEAT宏:
#include <iostream>
#define REPEAT(N, macro) REPEAT_##N(macro)
#define REPEAT_0(macro)
#define REPEAT_1(macro) REPEAT_0(macro) macro(0)
#define REPEAT_2(macro) REPEAT_1(macro) macro(1)
#define REPEAT_3(macro) REPEAT_2(macro) macro(2)
#define REPEAT_4(macro) REPEAT_3(macro) macro(3)
// etc...
// enum and class definitions
int main(){
#define MY_MACRO(N) case N: cout << Trait<N>::funct(i) << endl; break;
switch(i){
REPEAT(2, MY_MACRO)
}
}
我对这种方法的问题是我不能使用
REPEAT(END, MY_MACRO)
因为预处理器不知道我的枚举。
问题:有没有办法自动生成switch语句?
备注:
谢谢!
编辑1
更多说明:
编辑2/3
我决定在这里做一个补充,以更好地解释我的应用程序以及为什么我更喜欢其他人的解决方案
目前,在我的开关中我有一个看起来像这样的函数调用(in1,in2和out都是通过引用双重传递的,前两个情况是const)。
案例A:Trait :: funct(in1,in2,out);打破;
为什么我喜欢模板?
考虑案例Trait有2个函数funct1和funct2。我可以定义
template <int N>
class Trait {
public:
static int funct1(int i){static_assert(N!=N, "You forgot to define funct1");}
static int funct2(int i){static_assert(N!=N, "You forgot to define funct2");}
};
现在,如果缺少函数定义,编译器将返回有意义的错误。当其他人添加时,这将有所帮助。
使用基于Jarod42建议的C ++ 11特性的方法,我可以避免维护容易出错的长数组函数指针。
速度测试
到目前为止,我尝试了3个解决方案,但在Trait中只有两个成员函数:
前两个解决方案似乎是等效的,而基于开关的解决方案速度要快5倍。我使用gcc版本4.6.3和标志-O3。
答案 0 :(得分:2)
在C ++ 11中,您可以执行以下操作:
#if 1 // Not in C++11
#include <cstdint>
template <std::size_t ...> struct index_sequence {};
template <std::size_t I, std::size_t ...Is>
struct make_index_sequence : make_index_sequence < I - 1, I - 1, Is... > {};
template <std::size_t ... Is>
struct make_index_sequence<0, Is...> : index_sequence<Is...> {};
#endif
namespace detail {
template <std::size_t ... Is>
int funct(MyEnum e, int i, index_sequence<Is...>)
{
// create an array of pointer on function and call the correct one.
return std::array<int(*)(int), sizeof...(Is)>{{&Trait<MyEnum(Is)>::funct...}}[(int)e](i);
}
} // namespace detail
int funct(MyEnum e, std::size_t i)
{
return detail::funct(e, i, make_index_sequence<std::size_t(END)>());
}
注意:enum
不应该有洞(所以这里A=0
和B=1
就可以了)
以下宏可能有所帮助:
#define DYN_DISPATCH(TRAIT, NAME, SIGNATURE, ENUM, ENUM_END) \
namespace detail { \
template <std::size_t ... Is> \
constexpr auto NAME(ENUM e, index_sequence<Is...>) -> SIGNATURE \
{ \
return std::array<SIGNATURE, sizeof...(Is)>{{&TRAIT<ENUM(Is)>::NAME...}}[(int)e]; \
} \
} /*namespace detail */ \
template <typename...Ts> \
auto NAME(ENUM e, Ts&&...ts) \
-> decltype(std::declval<SIGNATURE>()(std::declval<Ts>()...)) \
{ \
return detail::NAME(e, make_index_sequence<std::size_t(ENUM_END)>())(std::forward<Ts>(ts)...); \
}
然后将其用作:
DYN_DISPATCH(Trait, funct, int(*)(int), MyEnum, END)
// now `int funct(MyEnum, int)` can be call.
答案 1 :(得分:2)
正如你所说,你的枚举是连续的。在这种情况下,您不需要任何模板或std::map
或switch
:
只使用一个函数指针数组,并使用枚举作为函数指针数组的索引!
#include <cassert>
#include <cstdio>
enum {
A,
B,
SIZE
};
int A_funct(int i) { return 3*i; }
int B_funct(int i) { return 24*i; }
typedef int (*enum_funct)(int );
enum_funct map[] = { A_funct, B_funct };
// In C++11 use this:
//static_assert( sizeof(map)/sizeof(map[0])==SIZE , "Some enum is missing its function!");
int main() {
assert(sizeof(map)/sizeof(map[0])==SIZE && "Some enum is missing its function!");
int i = 1;
std::printf("case A prints %d\n", map[A](i) );
std::printf("case B prints %d\n", map[B](i) );
}
更新:来自您的评论:
我对可维护性的唯一关注是明确写下来 5个不同的函数指针数组(如果我不自动执行此操作)。
好的,现在我了解维护问题。
我相信只有使用某种源代码生成,无论是使用函数指针数组还是switch
方法都可以实现这一点(无论是使用函数指针数组还是case
方法)你自己的源代码生成器。您还必须制定一些命名约定,以便可以自动生成函数指针数组(或switch
方法中#include <boost/preprocessor/repetition.hpp>
#define ENUM_SIZE 2
#define ENUM(z, n, unused) e##n,
enum {
BOOST_PP_REPEAT(ENUM_SIZE, ENUM, ~)
SIZE
};
#undef ENUM
int fA_e0(int i) { return 3*i; }
int fA_e1(int i) { return 24*i; }
int fB_e0(int i) { return 32*i; }
int fB_e1(int i) { return 8*i; }
typedef int (*enum_funct)(int );
#define MAP(z, n, case) f ## ##case ## _e##n,
enum_funct map_A[] = {
BOOST_PP_REPEAT(ENUM_SIZE, MAP, A)
};
enum_funct map_B[] = {
BOOST_PP_REPEAT(ENUM_SIZE, MAP, B)
};
#undef MAP
语句中的代码)。
由于你没有指定它,我只是编写了自己的命名约定。如果您对宏感到满意,那么我通过Boost Preprocessor Library
的一些盲目编辑与example:一起入侵g++ -E myfile.cpp
这是我们在预处理器解析了这些宏(enum { e0, e1, SIZE };
[...]
typedef int (*enum_funct)(int );
enum_funct map_A[] = {
fA_e0, fA_e1,
};
enum_funct map_B[] = {
fB_e0, fB_e1,
};
)之后得到的结果:
switch
因此,正如您所看到的,如果您指定自己的命名约定,则可以自动生成映射(函数指针数组)。 documentation很好。
但是,如果我是你,我会编写自己的源代码生成器。我会指定一个简单的文本文件格式(一行上的键值对,用空格分隔)并写入我自己的工具,从这个简单的文本文件生成所需的C ++源文件。然后,构建系统将在预构建步骤中调用我的源代码生成器工具。这样,你就不必乱用宏了。 (顺便说一句,我为自己编写了一个小测试框架,并且为了避免在C ++中缺乏反射,我使用自己的源代码生成器。真的不那么困难。)
前两个解决方案似乎是等效的,而基于的解决方案 开关速度提高了5倍。我使用gcc版本4.6.3与标志 -O3。
我必须看到你的源代码,生成的程序集以及你如何测量时间以了解它是如何发生的。
所以我也做了自己的速度测试。由于它会使这个答案变得混乱,因此源代码位于:switch approach和function pointer array方法。
正如我所料:switch
方法更快,但只有你有一些分支。 Andrei Alexandrescu在他的演讲中也说了同样的话
Writing Quick Code in C++, Quickly,大约38分钟。在我的机器上,如果枚举大小为5,则-O3 -flto
方法与函数指针数组方法一样快。如果枚举大小大于5,则函数指针数组方法始终更快。如果枚举大小为200且有10 ^ 8个函数调用,则在我的机器上运行速度提高10%以上。 (在线代码只有10 ^ 7个函数调用,否则会超时。)
(我使用链接时优化(case
标记到编译器和链接器),我只推荐它;它提供了很好的性能提升(在我的代码中2.5x)并且你唯一需要做的就是传递一个额外的标志。但是,在你的情况下,代码是如此简单,以至于它没有改变任何东西。如果你想尝试它:链接时间优化是要么不可用,要么只在gcc 4.6.3中进行实验。)
来自您的评论:
我按照您的基准方法逐步进行了新的实验 但是我仍然可以通过switch语句获得更好的结果(当时 枚举大小为150,开关仍然几乎是其两倍 带指针的解决方案)。 [...] 在使用我的代码进行的测试中,切换方法总是更好。我也跑了一些 用你的代码进行实验,我得到了同样的结果。
我查看了生成的汇编代码,至少有5个函数(5 switch
s)。如果我们至少有这么多函数,粗略地说,发生的是编译器将switch
方法转换为函数指针方法,但有一个明显的缺点。即使在最好的中也是如此case ,default:
总是经过1个额外的分支(整数比较,可能跟着跳转),与调度到要调用的函数时的手动编码函数指针数组方法相比。这个额外的分支属于case
标签,即使你故意在C ++代码中省略它,它也会生成;没有办法阻止编译器为此生成代码。 (如果你最多有4个switch
s 和,那么所有4个函数调用都可以内联,那么它就不同了;但是你已经有50个案例,所以它并不重要。)
除此之外,使用case:
方法,会生成额外的(冗余)指令和填充,与switch
标签上的代码相对应。这可能会增加您的缓存未命中。所以,正如我所看到的,switch
总是不如函数指针方法,如果你有多个案例(我的机器上有5个案例)。这也是他演讲中的Andrei Alexandrescu says;他给出了约7例的限制。
至于你的速度测试表明相反的原因:这些速度测试总是不可靠的,因为它们对于对齐和缓存非常敏感。然而,在我的原始测试中,切换方法总是比函数指针数组稍差,这与我上面对汇编代码的分析一致。
函数指针数组的另一个优点是它可以在运行时构建和更改;这是ENUM_SIZE=42
方法无法实现的。
奇怪的是我用函数指针获得的速度 数组的变化取决于枚举大小(我希望它是 大致不变)。
随着枚举大小的增加,您将拥有更多功能,并且更有可能发生指令缓存未命中。换句话说,如果你有更多的功能,程序应该运行稍慢。 (它在我的机器上。)当然整个事情是随机发生的,所以会有很大的偏差,如果41
比{{1}}跑得快,不要感到惊讶。如前所述,对齐会为此增加额外的噪音。
答案 2 :(得分:1)
您根本不需要模板来执行此操作。更像是好老X macros
#define MY_ENUM_LIST VAL(A) VAL(B)
// define an enum
#define VAL(x) x,
enum MyEnum { MY_ENUM_LIST END };
#undef VAL
// define a few functions doing a switch on Enum values
void do_something_with_Enum (MyEnum value, int i)
{
switch (value)
{
#define VAL(N) case N: std::cout << Trait<N>::funct(i) << std::endl; break;
MY_ENUM_LIST
#undef VAL
}
}
int do_something_else_with_Enum (MyEnum value)
{
switch (value)
{
#define VAL(x) case x: yet_another_template_mayhem(x);
MY_ENUM_LIST
#undef VAL
}
}
我已经浪费了足够的时间。如果您认为模板是解决方案,只需将问题更改为&#34;模板专家,预处理器不够好&#34;什么的。
您不会是第一个在无用的模板上浪费时间的人。许多人为解决不存在的问题提供臃肿无用的解决方案。
此外,您假设开关比函数指针数组更快是值得商榷的。这完全取决于枚举中的值的数量以及case语句中代码的可变性。
现在,如果优化不是一个大问题,你可以简单地使用虚方法来专门化你的枚举选择的任何对象的行为,并让编译器处理整个&#34;自动切换&#34;给你的东西。
这种方法的唯一好处是避免重复代码,如果你的对象足够相似,使你认为你会比编译器以专门的方式处理它们做得更好。
您似乎要求的是优化未知代码模式的通用解决方案,这是一个矛盾。
编辑:感谢Jarod42清理示例。
答案 3 :(得分:0)
看起来你想将每个函数关联和整数id,并通过id找到函数。
如果您的id是顺序的,您可以拥有一个由该id索引的函数指针数组,这将为您提供O(1)查找复杂性,例如:
typedef int Fn(int);
enum FnId {
A,
B,
FNID_COUNT
};
int fn_a(int);
int fn_b(int);
Fn* const fns[FNID_COUNT] = {
fn_a,
fn_b
};
int main() {
fns[A](1); // invoke function with id A.
}
如果id不是顺序的,你仍然可以有一个排序的{id, function_ptr}
元组数组,并对它进行二进制搜索,O(lg(N))查找复杂度。
这些都不需要宏或模板。
答案 4 :(得分:0)
对于数字(数据库)类型标识符,我有一个包含标识符的模板。通过可变参数模板的调度调用具有匹配类型特征的仿函数:
#include <iostream>
#include <stdexcept>
// Library
// =======
class TypeIdentifier
{
public:
typedef unsigned Integer;
enum Value
{
Unknown,
Bool,
Int8,
UInt8,
Int16,
UInt16,
Int32,
UInt32,
Int64,
UInt64,
Float,
Double,
String,
LargeObject,
Date,
Time,
DateTime
};
template <Value ...Ids> struct ListType {};
typedef ListType<
Bool,
Int8,
UInt8,
Int16,
UInt16,
Int32,
UInt32,
Int64,
UInt64,
Float,
Double,
String,
LargeObject,
Date,
DateTime,
// Always the last value:
Unknown
>
List;
public:
TypeIdentifier(Integer value = Unknown)
: m_id(value)
{}
Integer id() const { return m_id; }
/// Involve a functor having a member function 'Result apply<Traits>()'.
template<typename Functor>
typename Functor::result_type dispatch(const Functor&);
private:
Integer m_id;
};
template<TypeIdentifier::Value I>
struct TypeTraits
{
static constexpr TypeIdentifier::Value Id = I;
static constexpr bool is(TypeIdentifier::Integer id) { return (Id == id); }
static bool is(TypeIdentifier type_identifier) { return (Id == type_identifier.id()); }
// And conversion functions
};
namespace TypeIdentifierDispatch {
template <typename Functor, TypeIdentifier::Value I, TypeIdentifier::Value ... Ids> struct Evaluate;
template <typename Functor>
struct Evaluate<Functor, TypeIdentifier::Unknown> {
static typename Functor::result_type
apply(TypeIdentifier::Integer id, const Functor&) {
throw std::logic_error("Unknown Type");
}
};
template <typename Functor, TypeIdentifier::Value I, TypeIdentifier::Value ... Ids>
struct Evaluate {
static typename Functor::result_type
apply(TypeIdentifier::Integer id, const Functor& functor) {
if(TypeTraits<I>::is(id))
return functor.template apply<TypeTraits<I>>();
else return Evaluate<Functor, Ids...>::apply(id, functor);
}
};
template <typename Functor, TypeIdentifier::Value ... Ids>
inline typename Functor::result_type
evaluate(TypeIdentifier::Integer id, const Functor& functor, TypeIdentifier::ListType<Ids...>)
{
return Evaluate<Functor, Ids...>::apply(id, functor);
}
} // namespace TypeIdentifierDispatch
template<typename Functor>
inline
typename Functor::result_type TypeIdentifier::dispatch(const Functor& functor) {
return TypeIdentifierDispatch::evaluate(id(), functor, TypeIdentifier::List());
}
// Usage
// =====
struct Print {
typedef void result_type;
template <typename Traits>
result_type apply() const {
std::cout << "Type Identifier: " << Traits::Id << '\n';
}
};
inline void print_identifier(unsigned value) {
TypeIdentifier(value).dispatch(Print());
}
int main ()
{
print_identifier(TypeIdentifier::String);
return 0;
}
向库中添加新类型需要调整TypeIdentfier并(可能)添加专门的TypeTraits。
请注意,枚举值可以是任意的。
答案 5 :(得分:0)
使用递归模板,您可以自动生成等同于
的构造 if (i = A)
Trait<A>::funct(i);
else if (i = B)
Trait<B>::funct(i);
我认为它的性能类似于switch语句。您的原始示例可以如下重写。
#include <iostream>
using namespace std;
enum MyEnum {
A,
B,
END
};
template <MyEnum N>
class Trait
{ public:
static int funct(int i)
{
cout << "You forgot to define funct" << i;
return i;
}
};
template<>
class Trait<A> {
public:
static int funct(int i) { return i * 3; }
};
template<>
class Trait<B> {
public:
static int funct(int i) { return i * 24; }
};
template <MyEnum idx>
int Switch(const MyEnum p, const int n)
{
return (p == idx) ? Trait<idx>::funct(n) : Switch<(MyEnum)(idx - 1)>(p, n);
}
template <>
int Switch<(MyEnum)(0)>(const MyEnum p, const int n)
{
return Trait<(MyEnum)(0)>::funct(n);
}
int funct(MyEnum n)
{
return Switch<END>(n, n);
}
int main() {
MyEnum i = B;
cout << funct(i);
}