Почему список инициализаторов можно использовать только при объявлении? - PullRequest
14 голосов
/ 09 мая 2019

Массивы можно инициализировать с помощью так называемого списка инициализации.

Например:

int my_array[3] = {10, 20, 30};

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

my_array = {10, 20, 30};

error: assigning to an array from an initializer list

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

Мой вопрос: есть ли причина для того, чтобы такая функция была во время объявления, но не один размассив объявлен?Почему это работает в одном случае, но не в другом?

Ответы [ 3 ]

22 голосов
/ 09 мая 2019

Массивы - граждане второго сорта в C ++. Они являются объектами, но они строго ограничены они не могут быть скопированы, они разлагаются на указатели в разных контекстах и ​​т. д. Рассмотрите возможность использования std::array, которая является оболочкой (фиксированного размера) поверх встроенных массивов, но это первоклассный гражданин, который поддерживает различные удобства:

std::array<int, 3> my_array = {10, 20, 30};
my_array = {40, 50, 60};

Это работает, потому что для [array.overview] / 2 ,

std::array - это агрегатный тип, который можно инициализировать списком с помощью в N элементы, типы которых могут быть преобразованы в T.

живая демоверсия

Это также работает с std::vector. Векторы это другая история, поэтому я не буду вдаваться в подробности.


Если вы предпочитаете настаивать на встроенных массивах, вот обходной путь, который я разработал, чтобы включить присваивание списка значений встроенному массиву (уважая ценностные категории), используя методы метапрограммирования шаблонов. Ошибка времени компиляции (правильно) возникает если длина массива и список значений не совпадают. (Благодаря Caleth комментарий для указания на это!) Обратите внимание, что копирование встроенных массивов невозможно в 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.

живое демо

1 голос
/ 12 мая 2019

Есть ли причина наличия такой функции во время объявления, но не после объявления массива?Почему это работает в одном случае, а не в другом?

Синтаксис 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) инициализация именованной переменной с помощью фигурного списка инициализации после равенстваsign .

Синтаксис, что не работает для вас (myArr = {a, b, ...}), нумеруется как (10) и называется list-initialization в выражении присваивания.Особенность выражений присваивания состоит в том, что левая сторона должна быть так называемым 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);
}
1 голос
/ 09 мая 2019

Массивы могут быть инициализированы так называемым списком инициализации.

Ну, нет.

Класс может быть инициализирован с помощью списка инициализации, он должен иметь конструктор, который принимает std::initializer_list.

Пример :

vector( std::initializer_list<T> init, const Allocator& alloc = Allocator());

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

Агрегат имеет один из следующих типов:

  • тип массива
  • ...

И как LF сказал: их нельзя скопировать:

Назначение

Объекты массиватип не может быть изменен целиком: даже если они являются l-значениями (например, можно использовать адрес массива), они не могут появляться в левой части оператора присваивания

Источник: https://en.cppreference.com/w/cpp/language/array

Вот почему синтаксис {} работает для инициализации, а не для присваивания, потому что это не означает то же самое.

...