Копирование конструкции в списках инициализаторов

Я изучал уродливый мир std::intializer_list,

Насколько я понял из стандарта:

§ 11.6.4:

  1. Объект типа std::initializer_list создается из списка инициализаторов, как если бы реализация генерировала и материализовала (7.4) значение типа "массив из N const E", где N - количество элементов в списке инициализатора. Каждый элемент этого массива инициализируется с помощью соответствующего элемента списка инициализатора, и объект std::initializer_list создается для ссылки на этот массив. [Примечание: конструктор или функция преобразования, выбранные для копии, должны быть доступны (раздел 14) в контексте списка инициализатора. - конец примечания] [...]

Итак, в случае типа E это класс, я ожидаю, что будет вызван конструктор копирования.


Следующий класс не допускает конструирование копирования:

struct NonCopyable {
  NonCopyable() = default;   
  NonCopyable(const NonCopyable&) = delete;
};

Я собираюсь попытаться создать экземпляр std::initializer_list с этим классом.

#include <vector>

void foo() {
  std::vector<NonCopyable>{NonCopyable{}, NonCopyable{}};
}

С g++-8.2 -std=c++14 Я получаю то, что ожидаю, ошибка компилятора:

error: use of deleted function 'NonCopyable::NonCopyable(const NonCopyable&)',

Отлично!


Однако поведение меняется с новым стандартом.

В самом деле, g++-8.2 -std=c++17 компилирует.

Тест компилятора


Я думал, что это из-за нового требования о copy elision сначала по новому стандарту.

Однако, изменяя реализацию стандартной библиотеки (сохраняя C++17), ошибка возвращается:

clang-7 -std=c++17 -stdlib=libc++ терпит неудачу:

'NonCopyable' has been explicitly marked deleted here NonCopyable(const NonCopyable&) = delete;

Тест компилятора


Так чего мне не хватает?

1) Требует ли C++17 копирования-исключения в конструкции копирования элементов initializer_list?

2) Почему libc++ реализация не компилируется здесь?


Изменить Обратите внимание, что в примере g++ -std=c++17 (который компилируется), если я изменяю конструктор по умолчанию как "определенный пользователем":

struct NonCopyable {
  NonCopyable();
  NonCopyable(const NonCopyable&) = delete;
};

программа больше не компилируется (не из-за ошибки ссылки).

Пример компилятора

2 ответа

Решение

Проблема в том, что этот тип:

struct NonCopyable {
  NonCopyable() = default;   
  NonCopyable(const NonCopyable&) = delete;
};

является тривиально копируемым. Так что в качестве оптимизации, так как std::initializer_list просто поддерживается массивом, то, что делает libstdC++ - это просто записывает все содержимое в vector в качестве оптимизации. Обратите внимание, что этот тип легко копируется, даже если у него есть конструктор удаленных копий!

Вот почему, когда вы делаете конструктор по умолчанию предоставленным пользователем (просто ; вместо = default;), вдруг больше не компилируется. Это делает тип более не копируемым, и, следовательно, путь memcpy исчезает.

Относительно того, является ли это поведение правильным, я не уверен (я сомневаюсь, что есть требование, что этот код не должен компилироваться? Я представил 89164 на всякий случай). Вы, конечно, хотите, чтобы libstdC++ использовал этот путь в случае тривиально копируемого файла, но, возможно, необходимо исключить этот случай? В любом случае вы можете сделать то же самое, дополнительно удалив оператор присваивания копии (что вы, вероятно, захотите сделать в любом случае), что также приведет к тому, что тип не будет тривиально копируемым.

Это не компилировалось в C++14, потому что вы не могли создать std::initializer_list - для инициализации копирования требуется конструктор копирования. Но в C++17 с гарантированным разрешением копирования конструкция std::initializer_list Это хорошо. Но проблема на самом деле построения vector полностью отделен от std::initializer_list (действительно, это общая красная сельдь). Рассматривать:

void foo(NonCopyable const* f, NonCopyable const* l) {
  std::vector<NonCopyable>(f, l);
}

Это компилируется в C++11 просто отлично... по крайней мере, начиная с gcc 4.9.

Требуется ли в C++17 копирование elision в конструкции копирования элементов initializer_list?

Инициализация элементов initializer_list никогда не гарантируется использование "копирования конструкции". Он просто выполняет инициализацию копирования. И то, вызывает ли инициализация копирования конструктор копирования или нет, полностью зависит от того, что происходит при инициализации.

Если у вас есть тип, который можно конвертировать из intи вы делаете Type i = 5;это инициализация копии. Но это не вызовет конструктор копирования; вместо этого он будет вызывать Type(int) конструктор.

И да, конструкция элементов массива initializer_list ссылки могут быть скопированы. Включая правила C++17 для гарантированного отбора.

При этом, что не поддается этим правилам является инициализация vector сам. vector должен скопировать объекты из initializer_listпоэтому у них должен быть доступный конструктор копирования. Каким образом реализации компилятора / библиотеки удается обойти это, неизвестно, но это определенно неконкретное поведение.

Другие вопросы по тегам