C ++ 11 Fun со списками инициализаторов, массивами и перечислениями - PullRequest
5 голосов
/ 22 декабря 2011

Фон

Списки инициализаторов C ++ 11 можно использовать для инициализации векторов и массивов с передачей аргументов конструкторам.

У меня есть фрагмент кода ниже, где я хотел бы инициализировать такой массив со всеми перечислениями eCOLORS от eCOLORS::First до eCOLORS::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>();
}

Ответы [ 5 ]

7 голосов
/ 22 декабря 2011

Вы можете сделать это с помощью шаблонов с переменными значениями и того, что я собираюсь назвать «трюком с индексами».

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, но вы можете эмулировать его в определенной степени ).

1 голос
/ 22 декабря 2011

Я не думаю, что вы можете сделать это с помощью списков инициализаторов. Это не та вещь, для которой они предназначены. Я думаю, что вы могли бы справиться с достойным обходным путем, определив итератор, который будет перебирать любое перечисление, имеющее член First и Last.

Но, во-первых, ваше определение Size не совсем верно ...

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

Объявление его constexpr означает, что его определение является постоянной времени компиляции. Таким образом, вы можете использовать его в аргументах шаблона и т. П.

У меня сейчас нет времени, чтобы создать класс диапазона для вас. Это несколько усложняется тем фактом, что значения перечисления и целые числа не являются взаимозаменяемыми для классов перечисления. Но это не так уж сложно. Вы могли бы использовать этот вопрос " Существует ли класс диапазона в C ++ 0x (он же C ++ 11) для использования с диапазоном, основанным на циклах? " в качестве отправной точки. В основном вы используете векторный инициализатор, который инициализируется из пары [начало, конец) в сочетании с классом диапазона, как описано в этом вопросе.

0 голосов
/ 20 августа 2015

Мне нравится твоя проблема. Давным-давно подобные вещи обрабатывались с помощью макросов 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 };

Я сохранил ваш размер (), но добавил несколько других шаблонов, чтобы сделать инициализацию, опустить вниз, легче для чтения.

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

Edit:

Параметр мусора в функции рекурсивной инициализации можно удалить с помощью:

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 голосов
/ 16 октября 2014

Решение MACRO.

#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 голосов
/ 22 декабря 2011

Не существует автоматического способа сделать это с помощью списков инициализаторов, но вы можете сделать это алгоритмически, если знаете первое и последнее значения перечисления, просто используя цикл for для вставки значений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...