Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 Fun with initializer lists, arrays, and enumerations

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>();
}
like image 492
kfmfe04 Avatar asked Dec 22 '11 15:12

kfmfe04


2 Answers

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).

like image 101
R. Martinho Fernandes Avatar answered Sep 21 '22 11:09

R. Martinho Fernandes


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.

like image 27
Omnifarious Avatar answered Sep 21 '22 11:09

Omnifarious