C++11中与初始化列表、数组和枚举有关的乐趣

5

背景

C++11初始化列表可以用于使用构造函数参数初始化向量和数组。

下面是一段代码,我想使用初始化程序列表将所有eCOLORS枚举从eCOLORS::FirsteCOLORS::Last初始化到数组中。

原始代码

由于所有信息在编译时已知,我认为有一种方法可以解决这个问题。

enum class eCOLORS 
{
    kBLUE=0, kGREEN, kRED, kPURPLE,
        First=kBLUE, Last=kPURPLE 
};

template< typename E >
size_t Size()
{
    return (size_t)(E::Last) - (size_t)(E::First) + 1;
}

struct Foo
{
    Foo( eCOLORS color ) { }
};

int main(int argc, char** argv)
{
    Foo a[2] = {
        { eCOLORS::kRED   },
        { eCOLORS::kGREEN }
    };  // works, but requires manual maintenance if I add another color

    /* how to feed v with all the enums from First to Last
       without hard-coding?
    Foo v[Size<eCOLORS>()] = {

    };
    */
}

丑陋的伪答案

目前的共识似乎是没有办法做到这一点。

我提出这个问题最初的意图是,我想自动创建一个Foo对象数组,其初始化仅基于eColors的枚举。我想要一个无需维护的解决方案,即使在添加更多eColors条目后也能正常工作。

使用来自此早期帖子的Enum类,我可以编写一个函数模板,给我需要的功能。即使不使用该Enum类,您仍然可以从eCOLORS::First循环到eCOLORS::Last,并进行一些丑陋的转换。

我的丑陋的伪答案很笨重(远不如编译时初始化列表),但至少它是零维护的。

注意:如果有更好的解决方案出现,我将相应地更新OP。

template <typename T, typename E>
std::vector< T >
Initialize_With_Enums()
{
  std::vector< T > v;
  for( auto p : Enum<E>() )
    v.push_back( T( p ));
  return v;
}

int main( int argc, char** argv )
{
  auto w = Initialize_With_Enum<Foo,eCOLORS>();
}

1
C++11 初始化列表不能用于初始化数组,虽然语法看起来相似,但实际上数组使用聚合初始化。 - ildjarn
5个回答

7
你可以使用可变参数模板和我称之为“索引技巧”来完成这个操作。
typedef std::underlying_type<eCOLORS>::type underlying;

// just a type to carry around variadic pack of numbers
template <underlying...> struct indices {};

// A template to build up a pack of Count numbers from first
// third parameter is an accumulator
template <underlying First, underlying Count, typename Acc = indices<>>
struct make_indices;

// base case
template <underlying X, underlying... Acc>
struct make_indices<X, 0, indices<Acc...>> { typedef indices<Acc...> type; };
// recursive build up of the pack
template <underlying First, underlying Count, underlying... Acc>
struct make_indices<First, Count, indices<Acc...>>
    : make_indices<First, Count-1, indices<First+Count-1, Acc...>> {};

size_t const max_colors = underlying(eCOLORS::Last) - underlying(eCOLORS::First)+1;

// shortcut
typedef make_indices<
          underlying(eCOLORS::First),
          max_colors
        >::type all_eCOLORS_indices;

// takes a dummy parameter with the pack we built
template <underlying... Indices>
std::array<eCOLORS, max_colors> const& all_colors(indices<Indices...>) {
    // convert each number to the enum and stick it in an static array
    static std::array<eCOLORS, max_colors> const all = {
        eCOLORS(Indices)...
    };
    return all;
}

std::array<eCOLORS, max_colors> const& all_colors() {
    // create a dummy object of the indices pack type and pass it
    return all_colors(all_eCOLORS_indices());
}

这假设所有枚举器都是连续的,并需要使用 std::underlying_type,但在 GCC 4.6 中不支持(会在 4.7 中支持,但你可以模拟到一定程度)。

2
这就是为什么我们需要更强大的静态反射。max_enumator<>is_enumator<>next_enumator<>。类型特征做了很多,但我们还可以使用更多。 - deft_code

1

我不认为您可以使用初始化列表来完成这个功能。这不是它们的预期用途。我认为您可以通过定义一个迭代器来遍历任何拥有FirstLast成员的枚举类型,从而实现一个不错的解决方案。

但首先,您对于Size的定义并不完全正确...

template< typename E >
constexpr size_t Size()
{
    return (size_t)(E::Last) - (size_t)(E::First) + 1;
}

将其声明为constexpr意味着它的定义是编译时常量,因此您可以在模板参数等中使用它。

我现在没有时间为您创建范围类。由于枚举值和整数在枚举类中不可互换,因此它有些复杂。但并不太难。您可以使用这个问题 "C++0x(又名C++11)中是否有用于范围for循环的范围类?" 作为起点。您可以基本上使用初始化为[begin,end)对的向量初始化器与像该问题中讨论的范围类一起使用。


0

宏解决方案。

#include <stdio.h>
#include <initializer_list>

#define COLORS(V,E) \
    V(RED) \
    V(GREEN) \
    E(BLUE)

#define COMMA(V) \
    V,

#define NCOMMA(V) \
    V

#define SCOMMA(V) \
    #V,

#define SNCOMMA(E) \
    #E

enum Colors {
    COLORS(COMMA,NCOMMA)
};

const char * colors[] = {
    COLORS(SCOMMA,SNCOMMA)
};

#define INIT_LIST(V) \
    { V(COMMA,NCOMMA) }

int main(int argc, char  **argv) {
    for ( auto i : INIT_LIST(COLORS) ) {
        printf("%s\n", colors[i]);
    }
}

0

使用初始化列表没有自动完成此操作的方法,但是如果您知道枚举的第一个和最后一个值,您可以通过使用 for 循环来插入这些值来进行算法处理。


有没有宏解决方案呢?当然,函数模板会更好一些... - kfmfe04
然而,您的算法假定在第一个和最后一个值之间存在枚举。 - John
@John:没错,不过这已经在问题的粗体部分中暗示了。 - Peter Alexander

0

我喜欢你的问题。很久以前,这种事情曾经用X宏来处理http://www.drdobbs.com/the-new-c-x-macros/184401387

虽然我是一个C++11新手,但经过一些尝试,我已经得到了某种解决方案(g++ 4.8.4):

enum class Symbols { FOO, BAR, BAZ, First=FOO, Last=BAZ };

我保留了你的Size()函数,但是添加了一些其他样板代码,使得它下面的初始化更易读。
template< typename E > constexpr size_t Size() { return (size_t)(E::Last) - (size_t)(E::First) + 1; }
template< typename E > constexpr size_t as_sizet( E s ) { return (size_t)s; }
template< typename E > constexpr E operator++( E& s, int ) { return (E)(1 + (size_t)s); }
template< typename E > constexpr bool operator<=( E& a, E& b ) { return (size_t)a < (size_t)b; }

这里有两个神奇的地方:

  • 我们返回一个初始化数组的引用(它本身是另一个模板参数)
  • 我们在递归调用期间使用垃圾参数初始化静态数组

就像这样:

template< typename E, typename EARR > 
constexpr EARR& init_array( EARR& zArr, E sym = E::First, E junk = E::Last )
{
    return sym <= E::Last ? init_array( zArr, sym++, zArr[ as_sizet( sym ) ] = sym ) : zArr;
}

最终,它与以下内容一起完成:
  • typedef
  • 数组的静态声明
  • 被初始化的数组引用

就像这样:

typedef Symbols SymbolArr[ Size<Symbols>() ];
static SymbolArr symbolArr;
SymbolArr& symbolArrRef = init_array<Symbols, SymbolArr>(symbolArr);

编辑:

递归初始化函数中的垃圾参数可以使用以下方法移除:

template< typename E > constexpr E next( E& s ) { return (E)(1 + (size_t)s); }

template< typename E, typename EARR > 
constexpr EARR& init_array( EARR& zArr, E sym = E::First )
{
    return sym <= E::Last ? init_array( zArr, next( zArr[ as_sizet( sym ) ] = sym ) ) : zArr;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接