C ++自动生成switch语句

时间:2014-01-17 12:42:39

标签: c++ macros enums switch-statement

考虑以下代码

#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语句?

备注:

  • 我必须使用它的情况要复杂得多,自动化的东西会非常有用。
  • 对我来说,使用switch语句非常重要,因为速度可以实现(速度对我的应用程序至关重要)。

谢谢!

编辑1

更多说明:

  • 重要的是交换机的生成取决于枚举中定义的END值。

编辑2/3

我决定在这里做一个补充,以更好地解释我的应用程序以及为什么我更喜欢其他人的解决方案

  • 在我的真实应用程序中,枚举包含近50个不同的值,并且将来会扩展(希望其他人)。枚举包含连续值。
  • 班级&#34;特质&#34;有超过1个成员函数(目前为5)。此外,我需要在5个不同的文件中使用所有这些。如果我不使用自动生成我需要的方式,我最终会编写很多代码,这些代码基本相同。
  • Trait的成员函数始终以相同的方式使用。
  • 目前,在我的开关中我有一个看起来像这样的函数调用(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中只有两个成员函数:

  • Jarod42建议的解决方案
  • nndru和Ali
  • 建议的一个简单的函数指针数组
  • 使用RETURN宏切换语句

前两个解决方案似乎是等效的,而基于开关的解决方案速度要快5倍。我使用gcc版本4.6.3和标志-O3。

6 个答案:

答案 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=0B=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::mapswitch

只使用一个函数指针数组,并使用枚举作为函数指针数组的索引!

#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 approachfunction 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);
}