(Примечание: этот вопрос касается того, что не нужно указывать количество элементов и по-прежнему разрешать непосредственную инициализацию вложенных типов.) В
этом вопросе обсуждаются варианты использования, оставленные для массива 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
from <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} };
Кроме того, количество инициализаторов ограничено количеством аргументов функции и шаблона, поддерживаемых реализацией.
TMP
ваш вопрос?Ответы:
Лучшее, что я могу придумать, это:
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 ++ сможет оптимизировать это так, чтобы это было так же быстро, как прямая инициализация.
источник
make_array
звонок.static_assert
и некоторый TMP, чтобы определить, когдаTail
неявно преобразован вT
, а затем использоватьT(tail)...
, но это осталось в качестве упражнения для читателя :)Я ожидал простого
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)... }; }
источник
std::array<ret, sizeof...(T)>
наreturn
заявлении. Это бессмысленно заставляет существовать конструктор перемещения для типа массива (в отличие от конструкции из -T&&
) в C ++ 14 и C ++ 11.Объединив несколько идей из предыдущих сообщений, вот решение, которое работает даже для вложенных конструкций (протестировано в 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]]]
(Для последнего вывода я использую свой симпатичный принтер .)
Собственно, давайте улучшим типовую безопасность этой конструкции. Нам обязательно нужно, чтобы все типы были одинаковыми. Один из способов - добавить статическое утверждение, которое я редактировал выше. Другой способ - включить, только
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)...}; }
В любом случае вам понадобится свойство вариативного
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()
возвращается методом временного копирования, который компилятор (с достаточным количеством флагов оптимизации!) Может рассматривать как rvalue или иным образом оптимизировать, иstd::array
является агрегатным типом, поэтому компилятор может выбрать наилучший из возможных методов построения. .Наконец, обратите внимание, что вы не можете избежать конструкции копирования / перемещения при
make_array
настройке инициализатора. Таким образом, уstd::array<Foo,2> x{Foo(1), Foo(2)};
него нет копирования / перемещения, ноauto x = make_array(Foo(1), Foo(2));
есть два копирования / перемещения в качестве аргументовmake_array
. Я не думаю, что вы можете улучшить это, потому что вы не можете передать список вариативных инициализаторов лексически помощнику и вывести тип и размер - если бы препроцессор имелsizeof...
функцию для вариативных аргументов, возможно, это можно было бы сделать, но не в пределах основного языка.источник
Использование синтаксиса конечного возврата
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источник
C ++ 11 будет поддерживать этот способ инициализации для (большинства?) Контейнеров std.
источник
std::vector<>
не нужно явное целое число, и я не уверен, зачемstd::array
.{...}
синтаксис подразумевает постоянную степень времени компиляции, поэтому ctor должен иметь возможность определить степень.std::initializer_list::size
не являетсяconstexpr
функцией и поэтому не может использоваться в таком виде. Однако есть планы из libstdc ++ (реализация, поставляемая с GCC) иметь свою версиюconstexpr
.Я знаю, что этот вопрос был задан довольно давно, но я чувствую, что существующие ответы все еще имеют некоторые недостатки, поэтому я хотел бы предложить свою слегка измененную версию. Ниже приведены моменты, на которые, я думаю, отсутствуют некоторые существующие ответы.
1. Не нужно полагаться на RVO
В некоторых ответах упоминается, что нам нужно полагаться на RVO, чтобы вернуть построенный
array
. Это неправда; мы можем использовать инициализацию списка копирования, чтобы гарантировать, что временные библиотеки никогда не будут созданы. Так что вместо:return std::array<Type, …>{values};
мы должны сделать:
return {{values}};
2. Убедитесь
make_array
в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, …>
вернуть вместо возврата astd::array<std::decay_t<T>, …>
.У этого подхода есть один недостаток: мы больше не можем возвращать
array
тип значения с квалификацией cv. Но в большинстве случаевarray<const int, …>
мы всеconst array<int, …>
равно использовали бы вместо чего-то вроде . Есть компромисс, но я считаю разумным. В C ++ 17std::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>, 0> make_array() noexcept { return {}; }
Применение:
constexpr auto arr = make_array(make_array(1, 2), make_array(3, 4)); static_assert(arr[1][1] == 4, "!");
источник
(Решение от @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; }
источник
make_array
как в ответе Puppy.Компактная реализация С ++ 17.
template <typename... T> constexpr auto array_of(T&&... t) { return std::array{ static_cast<std::common_type_t<T...>>(t)... }; }
источник
Если std :: array не является ограничением и у вас есть Boost, обратите внимание на
list_of()
. Это не совсем похоже на инициализацию массива типа C. Но близко.источник
Создайте тип производителя массива.
Он перегружается
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}] );
Ни одно из этих решений не выглядит хорошим.
Использование переменных ограничивает вас установленным компилятором ограничением на количество переменных и блокирует рекурсивное использование их
{}
для подструктур.В конце концов, действительно хорошего решения нет.
Что я делаю, так это пишу свой код так, чтобы он потреблял
T[]
иstd::array
данные, и данные агностически - мне все равно, что я его кормлю. Иногда это означает, что мой код пересылки должен аккуратно превращать[]
массивы вstd::array
прозрачные.источник