Обертка Constexpr над массивом C с конструкторами, подобными std::vector

Мне нужна оболочка constexpr над массивом C или std::array с некоторыми дополнительными конструкторами (аналогично конструкторам std::vector):

template<class T, int N>
struct wrapper {
  T data[N];

  constexpr wrapper(int s);         // a
  constexpr wrapper(int j, int k);  // b

  constexpr wrapper(...values...)   // c
  : data(...values...) {} 
};

Я пытаюсь получить то же поведение, что и конструкторы std::vector, то есть:

constexpr wrapper<T,2> w(1);   // calls a
constexpr wrapper<T,2> w(1,2); // calls b
constexpr wrapper<T,2> w{1};   // calls c
constexpr wrapper<T,2> w{1,2}; // calls c

Примечание 1: идеальный конструктор пересылки:

template<class... Args>
constexpr wrapper(T&& t, Args&&... args) 
: data(std::forward<T>(t), std::forward<Args>(args)...) {}

победит других конструкторов.

Примечание 2: T[N]/std::array<T,N> не имеет конструкторов std::initializer_list<T>, поэтому следующее тоже не работает:

constexpr wrapper(std::initializer_list<T> v) : data(std::move(v)) {}

Примечание 3: если значения не присвоены в списке инициализации конструктора, тип оболочки не будет работать в константных выражениях.


person gnzlbg    schedule 10.10.2014    source источник


Ответы (1)


Конечно, просто используйте index_sequence и делегирующий конструктор:

  constexpr wrapper(std::initializer_list<T> v)
    : wrapper(std::make_index_sequence<N>{}, v) {}
private:
  template<std::size_t... Is> constexpr wrapper(
      std::index_sequence<Is...>, std::initializer_list<T> v)
    : data{v.begin()[Is]...} {}

Это работает, потому что согласно [support.initlist.access] аксессорами initializer_list являются constexpr; его тип итератора — E const*, поэтому мы можем получить доступ к любому определенному члену его массива во время компиляции.

Обратите внимание, что если вы укажете слишком короткий список инициализации в фигурных скобках (например, v.size() < N), то это не удастся; во время компиляции, если используется в константном выражении, и с неопределенным поведением в противном случае. Вот решение, построение по умолчанию T, если разрешено, и исключение в противном случае:

  constexpr static T too_short(std::true_type) { return {}; }
  T too_short(std::false_type) {
    throw std::invalid_argument("braced-init-list too short"); }
  template<std::size_t... Is> constexpr wrapper(
      std::index_sequence<Is...>, std::initializer_list<T> v)
    : data{v.size() > Is ? v.begin()[Is]
        : too_short(std::is_default_constructible<T>{})...} {}

Если вы хотите, чтобы инициализация всегда завершалась ошибкой при вызове со слишком коротким списком инициализации в фигурных скобках, просто замените правую ветвь условного оператора выражением throw:

  constexpr wrapper(std::initializer_list<T> v)
    : wrapper(std::make_index_sequence<N>{}, v.size() == N ? v
        : throw std::invalid_argument("braced-init-list incorrect length")) {}
private:
  template<std::size_t... Is> constexpr wrapper(
      std::index_sequence<Is...>, std::initializer_list<T> v)
    : data{v.begin()[Is]...} {}

Невозможно, кроме как в постоянном выражении, переносимо определить во время компиляции, что список инициализации в фигурных скобках слишком короткий (или слишком длинный!), потому что initializer_list скрывает эту информацию от системы типов.

Полный пример.

person ecatmur    schedule 10.10.2014