Подтвердить что ты не робот

Различное поведение инициализации списка С++ 11

Обратите внимание на следующий код:

class A {
private:
  std::string s;
public:
  A() = delete;
  A(const A&) = delete;
  A(A&&) = delete;
  A(const std::string &a) : s(a) {}
};

Теперь я хотел бы инициализировать массив инициализации списка использования A. g++ (4.9.1) может успешно построить следующий код:

int main() {
  A arr[2] = {{"a"}, {"b"}};
  return 0;
}

Но это не удалось для следующего кода:

class Aggr {
private:
  A arr[2];
public:
  Aggr() : arr{{"a"}, {"b"}} {}
};

Сообщения об ошибках:

test.cc: In constructor ‘Aggr::Aggr()’:
test.cc:22:28: error: use of deleted function ‘A::A(A&&)’
   Aggr() : arr{{"a"}, {"b"}} {}
                            ^          
test.cc:11:3: note: declared here
   A(A&&) = delete;
   ^

Тем не менее, инициализатор списка пытается вызвать конструктор перемещения для инициализации массива внутри класса. Однако этот код был успешно создан clang v3.5 без каких-либо предупреждений. Итак, я хотел бы знать, что С++ 11 (или более поздняя версия) определяет правила в отношении инициализации списка. Спасибо заранее.

4b9b3361

Ответ 1

Чтение снова и снова стандарта, я думаю, что это ошибка.

Что говорит стандарт?

8.5.1/2. Когда агрегат инициализируется списком инициализаторов, как указано в 8.5.4, элементы списка инициализаторов берутся как инициализаторы для членов совокупности, увеличивая индекс или порядок членов. Каждый член инициализируется с помощью соответствующее предложение инициализатора.

Объясняется, что:

8.5/14: (...) называется копией-инициализацией. [Примечание. Копирование-инициализация может вызвать ход (12.8). -end note]

Но я не нашел доказательств в 12.8, что в вашем конкретном случае потребуется переход.

8.5.4/3 В противном случае, если T - тип класса, рассматриваются конструкторы. Если T имеет конструктор-инициализатор-список, аргумент список состоит из списка инициализаций как один аргумент; в противном случае, список аргументов состоит из элементов списка инициализаторов. Соответствующие конструкторы перечислены и выбираются лучшие через разрешение перегрузки (13.3).

Итак, в принципе, код должен работать!

Это ошибка? Попытка экспериментального способа

Я прокомментировал удаление конструктора перемещения, чтобы воспользоваться неявным конструктором перемещения. Как ни странно, я получил следующее сообщение об ошибке:

    Compilation error   time: 0 memory: 3232 signal:0

prog.cpp: In constructor 'Aggr::Aggr()':
prog.cpp:19:28: error: use of deleted function 'A::A(const A&)'
   Aggr() : arr{{"a"}, {"b"}} {}
                            ^
prog.cpp:10:3: note: declared here
   A(const A&) = delete  

Итак, теперь он жалуется на недостающий конструктор копий!

Еще более странно, я тогда предоставил свой собственный конструктор перемещения вместо неявного: здесь он скомпилировал код успешно!

Наконец, я предоставил как копию, так и переход и добавил некоторые трассировки:

class A {
private:
  std::string s;
public:
  A() = delete;
  A(const A&)  { std::cout<<"copy\n";} //= delete;
  A(A&&) { std::cout<<"move\n";} //= delete;
  A(const std::string &a) : s(a) {  std::cout<<"string ctor\n";}
};

И когда я создаю объект Aggr, он просто отображает:

string ctor 
string ctor

показывающий, что элемент массива инициализирован, формирует конструктор строк, используя копию elision, как мы и ожидали.

Все эти тесты были выполнены с помощью gcc-9.4.2 на ideone с параметром С++ 14.

Заключение

Тот факт, что тот же код не скомпилирован с неявным движением ctor и преуспевает с пользовательским движением ctor, выглядит очень серьезно, как ошибка.

Тот факт, что конструктор перемещения не используется, когда он доступен, усиливает это впечатление.

Следовательно, я сообщил эту ошибку.