Копирование конструкции в списках инициализаторов - PullRequest
0 голосов
/ 03 февраля 2019

Я исследовал уродливый мир 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 copy-elision в копии конструкции элементов initializer_list?

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


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

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

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

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

Ответы [ 2 ]

0 голосов
/ 03 февраля 2019

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

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

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

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

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

Это не компилируетсяв C ++ 14, потому что вы не могли создать std::initializer_list - copy-initialization там требуется конструктор копирования.Но в 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.

0 голосов
/ 03 февраля 2019

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

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

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

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

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

...