5

Background

C++11 initializer lists can be used to initialize vectors and arrays with argument passing to constructors.

I have a piece of code below where I would like to initialize such an array with all the enumerations of eCOLORS from eCOLORS::First to eCOLORS::Last using initializer lists.

Original Code

Since all information is known at compile time, I think there is a way to solve this problem.

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>()] = {

    };
    */
}

Ugly Pseudo-Answer

The consensus appears to be that there is currently no way to this.

My original intent in asking this question is, I want to automagically create an array of Foo objects whose initialization is solely based on the enumeration of eColors. I wanted a no maintenance solution that would work even after you add more entries into eColors.

Using the Enum class from this earlier post, I can write a function template that gives me the functionality that I need. Even without using that Enum class, you could still loop from eCOLORS::First to eCOLORS::Last, along with some ugly casts.

My ugly pseudo-answer is kludgy (nowhere as nice as a compile-time initializer list), but at least it is zero maintenance.

NOTE: if better solutions come up, I will update the OP accordingly.

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>();
}
Community
  • 1
  • 1
kfmfe04
  • 14,936
  • 14
  • 74
  • 140
  • 1
    C++11 initializer lists _cannot_ be used to initialize arrays -- while the syntax appears similar, arrays actually use aggregate initialization. – ildjarn Dec 22 '11 at 19:25

5 Answers5

7

You can do this with variadic templates and what I'm going to call the "indices trick".

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());
}

This assumes all the enumerators are sequential, and needs std::underlying_type which is not supported in GCC 4.6 (will be in 4.7, but you can emulate it to a certain extent).

Community
  • 1
  • 1
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • 2
    This is why we need more powerful static reflection. `max_enumator<>`, `is_enumator<>`, `next_enumator<>`. Type traits does a lot but we could use more. – deft_code Dec 22 '11 at 17:01
1

I don't think you can do this with initializer lists. This isn't the sort of thing they're meant for. I think you could manage a decent workaround by defining an iterator that would iterate over any enumeration that had a First and Last member.

But first, your definition of Size isn't quite right...

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

Declaring it constexpr means that it's definition is a compile time constant. So you can use it in template arguments and the like.

I don't have time right now to create the range class for you. It's somewhat complicated by the fact that enum values and integers are not interchangeable for enum classes. But it's not too hard. You might use this question "Is there a range class in C++0x (aka C++11) for use with range based for loops?" as a starting point. You basically use the vector initializer that initializes from a [begin, end) pair in conjuction with a range class like is discussed in that question.

Community
  • 1
  • 1
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
0

The MACRO solution.

#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]);
    }
}
Alexander Oh
  • 24,223
  • 14
  • 73
  • 76
0

I like your problem. Long ago, this kind of thing used to be handled with X macros http://www.drdobbs.com/the-new-c-x-macros/184401387

I'm a c++11 newb, but after some fiddling around I've got some kind of solution (g++ 4.8.4):

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

I kept your Size() but added some other boilerplate to make the initialization, lower down, easier to read.

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; }

There are two bits of magic here:

  • We return a reference to the initialized array (itself another template parameter)
  • We initialize a static array during the recursive call using a junk argument

Like so:

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;
}

In the end, it comes together with:

  • The typedef
  • The static declaration of the array
  • The reference to the array that gets initialized

Like so:

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

Edit:

The junk parameter in the recursive initialization function can be removed using:

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;
}
0

There isn't an automatic way to do this using initializer lists, but you could do it algorithmically if you know the first and last values of the enum by just using a for loop to insert the values.

Peter Alexander
  • 53,344
  • 14
  • 119
  • 168