Как эмулировать поведение инициализации массива C "int arr [] = {e1, e2, e3, ...}" с помощью std :: array? - PullRequest
132 голосов
/ 24 мая 2011

(Примечание: этот вопрос о том, что нет необходимости указывать количество элементов и все же разрешать непосредственную инициализацию вложенных типов.)
В этом вопросе обсуждается использование слевадля массива C, например int arr[20];.На его ответе , @James Kanze показывает одну из последних цитаделей массивов C, это уникальные характеристики инициализации:

int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 };

Нам не нужно указывать количество элементов, ура!Теперь итерируйте его с помощью функций C ++ 11 std::begin и std::end из <iterator> ( или ваших собственных вариантов ), и вам даже не нужно думать о его размере.

Теперь, есть ли (возможно, TMP) способы добиться того же с std::array?Использование макросов позволило сделать его лучше.:)

??? std_array = { "here", "be", "elements" };

Редактировать : Промежуточная версия, составленная из различных ответов, выглядит следующим образом:

#include <array>
#include <utility>

template<class T, class... Tail, class Elem = typename std::decay<T>::type>
std::array<Elem,1+sizeof...(Tail)> make_array(T&& head, Tail&&... values)
{
  return { std::forward<T>(head), std::forward<Tail>(values)... };
}

// in code
auto std_array = make_array(1,2,3,4,5);

И использует все классные C++ 11 материалов:

  • Вариативные шаблоны
  • sizeof...
  • rvalue ссылки
  • совершенная пересылка
  • std::array, конечно
  • равномерная инициализация
  • без учета возвращаемого типа с равномерной инициализацией
  • вывод типа (auto)

И пример может здесь .

Однако , как @Johannes указывает в комментарии к ответу @ Xaade, вы не можете инициализировать вложенные типы с помощью такой функции.Пример:

struct A{ int a; int b; };

// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
??? std_array = { {1,2}, {3,4} };

Кроме того, количество инициализаторов ограничено количеством аргументов функций и шаблонов, поддерживаемых реализацией.

Ответы [ 9 ]

59 голосов
/ 24 мая 2011

Лучшее, что я могу придумать:

template<class T, class... Tail>
auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)>
{
     std::array<T, 1 + sizeof...(Tail)> a = { head, tail ... };
     return a;
}

auto a = make_array(1, 2, 3);

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

37 голосов
/ 24 мая 2011

Я бы ожидал простого make_array.

template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) {
    // return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } };
    return { std::forward<T>(refs)... };
}
20 голосов
/ 08 июня 2011

Объединяя несколько идей из предыдущих постов, вот решение, которое работает даже для вложенных конструкций (протестировано в GCC4.6):

template <typename T, typename ...Args>
std::array<T, sizeof...(Args) + 1> make_array(T && t, Args &&... args)
{
  static_assert(all_same<T, Args...>::value, "make_array() requires all arguments to be of the same type."); // edited in
  return std::array<T, sizeof...(Args) + 1>{ std::forward<T>(t), std::forward<Args>(args)...};
}

Как ни странно, не может сделать возвращаемое значение ссылкой на rvalue, чтоне будет работать для вложенных конструкций.В любом случае, вот тест:

auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))),
                    make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))),
                    make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))),
                    make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4")))
                    );

std::cout << q << std::endl;
// produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]]

(Для последнего вывода я использую мой pretty-printer .)


Собственно, давайте улучшимТип безопасности этой конструкции.Нам определенно нужно, чтобы все типы были одинаковыми.Один из способов - добавить статическое утверждение, которое я редактировал выше.Другой способ - включить make_array, только если типы одинаковы, например:

template <typename T, typename ...Args>
typename std::enable_if<all_same<T, Args...>::value, std::array<T, sizeof...(Args) + 1>>::type
make_array(T && t, Args &&... args)
{
  return std::array<T, sizeof...(Args) + 1> { std::forward<T>(t), std::forward<Args>(args)...};
}

В любом случае вам понадобится признак типа * variadic all_same<Args...>.Здесь это обобщение из std::is_same<S, T> (обратите внимание, что затухание важно для обеспечения возможности смешивания T, T&, T const & и т. Д.):

template <typename ...Args> struct all_same { static const bool value = false; };
template <typename S, typename T, typename ...Args> struct all_same<S, T, Args...>
{
  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value && all_same<T, Args...>::value;
};
template <typename S, typename T> struct all_same<S, T>
{
  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value;
};
template <typename T> struct all_same<T> { static const bool value = true; };

Обратите внимание, что make_array() возвращает значениеВременная копия, которую компилятору (с достаточным количеством флагов оптимизации!) разрешено рассматривать как значение или как-либо иначе оптимизировать, а std::array является агрегатным типом, поэтому компилятор может выбрать лучший из возможных методов построения.

Наконец, обратите внимание, что вы не можете избежать конструкции копирования / перемещения, когда make_array устанавливает инициализатор.Таким образом, std::array<Foo,2> x{Foo(1), Foo(2)}; не имеет копирования / перемещения, но auto x = make_array(Foo(1), Foo(2)); имеет два копирования / перемещения, поскольку аргументы передаются в make_array.Я не думаю, что вы можете улучшить это, потому что вы не можете лексически передать список инициализатора переменной помощнику и , чтобы определить тип и размер - если препроцессор имел функцию sizeof... для аргументов переменнойвозможно, это можно было бы сделать, но не на базовом языке.

11 голосов
/ 16 января 2017

Использование синтаксиса конечного возврата make_array может быть дополнительно упрощено

#include <array>
#include <type_traits>
#include <utility>

template <typename... T>
auto make_array(T&&... t)
  -> std::array<std::common_type_t<T...>, sizeof...(t)>
{
  return {std::forward<T>(t)...};
}

int main()
{
  auto arr = make_array(1, 2, 3, 4, 5);
  return 0;
}

К сожалению, для агрегатных классов требуется явная спецификация типа

/*
struct Foo
{
  int a, b;
}; */

auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6});

На самом деле эта make_array реализация указана в операторе sizeof ...


C ++ 17 версия

Благодаря выводу аргумента шаблона для шаблонов классов предложению мы можем использовать руководства по выводу, чтобы избавиться от make_array помощника

#include <array>

namespace std
{
template <typename... T> array(T... t)
  -> array<std::common_type_t<T...>, sizeof...(t)>;
}

int main()
{
  std::array a{1, 2, 3, 4};
  return 0; 
}

Скомпилировано с флагом -std=c++1z под x86-64 gcc 7.0

6 голосов
/ 11 июля 2016

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

<ч />

1. Не нужно полагаться на RVO

В некоторых ответах упоминается, что нам нужно полагаться на RVO для возврата построенного array. Это неправда; мы можем использовать copy-list-initialization , чтобы гарантировать, что временные файлы никогда не будут созданы. Так что вместо:

return std::array<Type, …>{values};

мы должны сделать:

return {{values}};

2. Сделайте make_array a constexpr функцию

Это позволяет нам создавать постоянные массивы во время компиляции.

3. Не нужно проверять, чтобы все аргументы были одного типа

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

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array<int>(a, b, c);  // Will this work?

Если мы просто static_assert считаем, что a, b и c имеют один и тот же тип, то эта проверка завершится неудачно, но, вероятно, это не то, что мы ожидали. Вместо этого мы должны сравнить их std::decay_t<T> типы (которые все int s)).

4. Выведите тип значения массива, убрав переданные аргументы

Это похоже на пункт 3. Использование того же фрагмента кода, но на этот раз не указывайте тип значения явно:

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array(a, b, c);  // Will this work?

Мы, вероятно, хотим сделать array<int, 3>, но реализации в существующих ответах, вероятно, все не в состоянии это сделать. Что мы можем сделать, вместо того, чтобы вернуть std::array<T, …>, вернуть std::array<std::decay_t<T>, …>.

У этого подхода есть один недостаток: мы больше не можем возвращать array типа cv-квалифицированного значения. Но в большинстве случаев вместо array<const int, …> мы все равно использовали бы const array<int, …>. Есть компромисс, но я думаю, что разумный. C ++ 17 std::make_optional также использует этот подход:

template< class T > 
constexpr std::optional<std::decay_t<T>> make_optional( T&& value );
<ч />

С учетом вышеизложенного полная рабочая реализация make_array в C ++ 14 выглядит следующим образом:

#include <array>
#include <type_traits>
#include <utility>

template<typename T, typename... Ts>
constexpr std::array<std::decay_t<T>, 1 + sizeof... (Ts)>
make_array(T&& t, Ts&&... ts)
    noexcept(noexcept(std::is_nothrow_constructible<
                std::array<std::decay_t<T>, 1 + sizeof... (Ts)>, T&&, Ts&&...
             >::value))

{
    return {{std::forward<T>(t), std::forward<Ts>(ts)...}};
}

template<typename T>
constexpr std::array<std::decay<T>_t, 0> make_array() noexcept
{
    return {};
}

Использование:

constexpr auto arr = make_array(make_array(1, 2),
                                make_array(3, 4));
static_assert(arr[1][1] == 4, "!");
6 голосов
/ 24 мая 2011

C ++ 11 будет поддерживать этот способ инициализации для (большинства?) Стандартных контейнеров.

5 голосов
/ 30 декабря 2014

(Решение @dyp)

Примечание: требуется C ++ 14 (std::index_sequence).Хотя можно реализовать std::index_sequence в C ++ 11.

#include <iostream>

// ---

#include <array>
#include <utility>

template <typename T>
using c_array = T[];

template<typename T, size_t N, size_t... Indices>
constexpr auto make_array(T (&&src)[N], std::index_sequence<Indices...>) {
    return std::array<T, N>{{ std::move(src[Indices])... }};
}

template<typename T, size_t N>
constexpr auto make_array(T (&&src)[N]) {
    return make_array(std::move(src), std::make_index_sequence<N>{});
}

// ---

struct Point { int x, y; };

std::ostream& operator<< (std::ostream& os, const Point& p) {
    return os << "(" << p.x << "," << p.y << ")";
}

int main() {
    auto xs = make_array(c_array<Point>{{1,2}, {3,4}, {5,6}, {7,8}});

    for (auto&& x : xs) {
        std::cout << x << std::endl;
    }

    return 0;
}
0 голосов
/ 25 августа 2016

Создать тип создателя массива.

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

Добавить finish бесплатную функцию, которая принимает создатель массива и генерирует массив непосредственно из цепочки ссылок.

Синтаксис должен выглядеть примерно так:

auto arr = finish( make_array<T>->* 1,2,3,4,5 );

Не разрешает построение на основе {}, как только operator=. Если вы хотите использовать =, мы можем заставить его работать:

auto arr = finish( make_array<T>= {1}={2}={3}={4}={5} );

или

auto arr = finish( make_array<T>[{1}][{2}[]{3}][{4}][{5}] );

Ничто из этого не выглядит как хорошее решение.

Использование variardics ограничивает вас введенным компилятором ограничением на число переменных и блокирует рекурсивное использование {} для подструктур.

В конце концов, действительно нет хорошего решения.

Что я делаю, так это пишу свой код, чтобы он потреблял как T[], так и std::array данные независимо - ему все равно, какой я его кормлю Иногда это означает, что мой код пересылки должен аккуратно превратить [] массивы в std::array s прозрачно.

0 голосов
/ 24 мая 2011

Если std :: array не является ограничением и если у вас есть Boost, посмотрите на list_of(). Это не совсем то же самое, что инициализация массива типа C, которую вы хотите. Но близко.

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