Копирование конструкции в списках инициализаторов
Я изучал уродливый мир std::intializer_list
,
Насколько я понял из стандарта:
§ 11.6.4:
- Объект типа 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
поэтому у них должен быть доступный конструктор копирования. Каким образом реализации компилятора / библиотеки удается обойти это, неизвестно, но это определенно неконкретное поведение.