Почему список инициализаторов можно использовать только при объявлении?
Массивы могут быть инициализированы так называемым списком инициализации.
Например:
int my_array[3] = {10, 20, 30};
Это очень полезно, когда у нас есть набор начальных значений для нашего массива. Однако этот подход не работает для назначения новых значений массиву после его объявления.
my_array = {10, 20, 30};
error: assigning to an array from an initializer list
Однако иногда у нас есть процессы, в которых нам нужно несколько раз инициализировать наши массивы для некоторых начальных значений (например, внутри цикла), поэтому я думаю, что было бы очень полезно иметь возможность использовать списки инициализаторов для назначения значений уже объявленным переменным.
Мой вопрос: есть ли причина для того, чтобы иметь такую функцию во время объявления, но не после того, как массив объявлен? Почему это работает в одном случае, но не в другом?
Ответы
Ответ 1
Массивы являются гражданами второго сорта в C++. Это объекты, но они строго ограничены: их нельзя копировать, они разлагаются на указатели в различных контекстах и т.д. Рассмотрите возможность использования std::array
, который является оболочкой (фиксированного размера) поверх встроенных массивов, но это первоклассный гражданин, который поддерживает различные функции удобства:
std::array<int, 3> my_array = {10, 20, 30};
my_array = {40, 50, 60};
Это работает, потому что, согласно [array.overview]/2,
std::array
является агрегатным типом, который может быть инициализирован списком максимум с N
элементами, типы которых могут быть преобразованы в T
live demo
Это также работает с std::vector
. Векторы - это отдельная история, поэтому я не буду вдаваться в подробности.
Если вы предпочитаете настаивать на встроенных массивах, вот обходной путь, который я разработал, чтобы позволить присваивать список значений встроенному массиву (с учетом категорий значений), используя методы метапрограммирования шаблонов. Ошибка времени компиляции (правильно) возникает, если длина массива и список значений не совпадают. (Спасибо комментарию Калет за это!) Обратите внимание, что копирование встроенных массивов невозможно в C++; вот почему мы должны передать массив в функцию.
namespace detail {
template <typename T, std::size_t N, std::size_t... Ints, typename... Args>
void assign_helper(T (&arr)[N], std::index_sequence<Ints...>, Args&&... args)
{
((arr[Ints] = args), ...);
}
}
template <typename T, std::size_t N, typename... Args>
void assign(T (&arr)[N], Args&&... args)
{
return detail::assign_helper(arr, std::make_index_sequence<N>{}, std::forward<Args>(args)...);
}
И использовать это:
int arr[3] = {10, 20, 30};
assign(arr, 40, 50, 60);
Теперь arr
состоит из 40, 50, 60
.
live demo
Ответ 2
Массивы могут быть инициализированы так называемым списком инициализации.
Ну нет.
Класс может быть инициализирован с помощью списка инициализации, он должен иметь конструктор, который принимает std::initializer_list
.
Пример:
vector( std::initializer_list<T> init, const Allocator& alloc = Allocator());
Массивы не являются классами, поэтому у них не может быть конструктора. Но они могут быть инициализированы с помощью агрегатной инициализации:
Агрегат является одним из следующих типов:
И, как сказал Л.Ф., они не могут быть скопированы:
присваивание
Объекты типа массива нельзя изменить в целом: даже если они являются l-значениями (например, адрес массива может быть взят), они не могут появляться в левой части оператора присваивания.
Источник: https://en.cppreference.com/w/cpp/language/array
Вот почему синтаксис {}
работает для инициализации, а не для присваивания, потому что это не означает то же самое.
Ответ 3
Есть ли причина наличия такой возможности во время объявления, но не после объявления массива? Почему это работает в одном случае, но не в другом?
Синтаксис x = {a, b,...}
включает определенный тип списков инициализаторов, называемый copy-list-initialization. В cppreference упоминаются возможные способы использования инициализации списка копирования:
-
T object = {arg1, arg2,...};
(6) -
function( { arg1, arg2,... } )
(7) -
return { arg1, arg2,... } ;
(8) -
object[ { arg1, arg2,... } ]
(9) -
object = { arg1, arg2,... }
(10) -
U( { arg1, arg2,... } )
(11) -
Class { T member = { arg1, arg2,... }; };
(12)
Синтаксис массива, который вы попробовали T myArr[] = {a, b, c...}
работает и нумеруется как (6) инициализация именованной переменной с помощью фигурного списка инициализации после знака равенства.
Синтаксис, который не работает для вас (myArr = {a, b,...}
), нумеруется как (10) и называется инициализацией списка в выражении присваивания. Особенность выражений присваивания заключается в том, что левая сторона должна быть так называемым lvalue, и, хотя массивы являются lvalue, они не могут появляться в левой части присваиваний согласно спецификации.
При этом было бы не сложно обойти присваивание, скопировав список инициализатора в массив как таковой:
#include <algorithm>
#include <iostream>
int main() {
int arr[] = {1, 2, 3};
auto itr = arr;
auto assign = {5, 2, 1};
std::copy(assign.begin(), assign.end(), itr);
}