Приоритет инициализации списка от объекта того же типа

#include <iostream>
#include <initializer_list>
using namespace std;

struct CL
{
    CL(){}
    CL (std::initializer_list<CL>){cout<<1;}
    CL (const CL&){cout<<2;}
};

int main()
{
    CL cl1;
    CL cl2 {cl1}; //prints 21
}

Вот структура CL с конструктором копирования и конструктором списка инициализаторов. Я думаю, здесь должен быть вызван только конструктор копирования, потому что в соответствии со стандартом C ++ 14, 8.5.4 / 3

Список-инициализация объекта или ссылки типа T определяется следующим образом:
- Если T является типом класса, а список инициализатора имеет единственный элемент типа cv U, где U - это T или класс, производный от T , объект инициализируется из этого элемента (путем инициализации копирования для инициализации списка копирования или путем прямой инициализации для инициализации прямого списка).
- В противном случае ...

Другими словами, инициализация cl2 должна выполняться из элемента cl1, а не из списка инициализаторов {cl1}. Clang и gcc выводят 21, только Visual Studio выводит 2, и я думаю, что это правильно.
Есть два конструктора-кандидата для принятия аргумента cl1 типа CL:

  1. Конструктор с std::initializer_list<CL> (проходит, потому что такого преобразования из CL в std::initializer_list<CL> нет)
  2. Конструктор копирования с const CL & (точное совпадение только с квалификационным преобразованием non-const- ›const)

Кто прав? Чье поведение правильно?


person user3514538    schedule 28.11.2015    source источник
comment
Какой компилятор? Я не могу воспроизводить с лязгом. Я получаю 2, как и ожидалось.   -  person juanchopanza    schedule 28.11.2015
comment
@juanchopanza какая версия clang? ;)   -  person n. 1.8e9-where's-my-share m.    schedule 28.11.2015
comment
при использовании онлайн-компилятора: clang 3.6 говорит 21, а clang 3.7 говорит 2; все версии g ++ до 5.2 говорят 21   -  person M.M    schedule 28.11.2015
comment
@ n.m. Независимо от того, чему соответствует apple llvm 7.0.0.   -  person juanchopanza    schedule 28.11.2015
comment
В C ++ 11 было много проблем с инициализацией в фигурных скобках и попытками копирования-конструирования, на самом деле вызывающими какой-то другой конструктор ... некоторые из этих случаев были исправлены для C ++ 14   -  person M.M    schedule 28.11.2015
comment
Я использовал компилятор для rextester.com/l/cpp_online_compiler_clang. Есть clang 3.6 и gcc 4.9.2   -  person user3514538    schedule 28.11.2015
comment
Можете ли вы подтвердить, что используете опубликованный стандарт C ++ 14? (Если нет, и вы имеете в виду черновик, укажите, какой черновик)   -  person M.M    schedule 29.11.2015
comment
Будущим читателям: этот вопрос относится к старой версии стандарта (черновику). Чтобы узнать о более новом стандарте (проекте), обратитесь к этому вопросу.   -  person xskxzr    schedule 24.08.2018


Ответы (1)


tl; dr: В опубликованном тексте C ++ 14 указан вывод 21. Однако поведение этого кода было изменено CWG Issue 1467., получивший статус Дефект в ноябре 2014 года.

Отчеты о дефектах применяются задним числом. clang 3.7 и VS2015 применили решение, предложенное в этом отчете о дефекте, который появляется в черновиках C ++ 17 начиная с N4296.


До этого отчета о дефектах поведение описывалось в этом тексте из N4140 [over.match.list]:

Когда объекты неагрегированного типа класса T инициализируются списком (8.5.4), при разрешении перегрузки конструктор выбирается в два этапа:

  • Первоначально функции-кандидаты являются конструкторами списка инициализаторов (8.5.4) класса T, а список аргументов состоит из списка инициализаторов в виде единственного аргумента.
  • Если жизнеспособный конструктор списка инициализаторов не найден, снова выполняется разрешение перегрузки, где все функции-кандидаты являются конструкторами класса T, а список аргументов состоит из элементов списка инициализаторов.

Если в списке инициализаторов нет элементов и у T есть конструктор по умолчанию, первая фаза опускается. При инициализации списка копирования, если выбран явный конструктор, инициализация имеет неправильный формат. [Примечание: это отличается от других ситуаций (13.3.1.3, 13.3.1.4), где для инициализации копирования рассматриваются только конструкторы преобразования. Это ограничение применяется только в том случае, если эта инициализация является частью окончательного результата разрешения перегрузки. —В конце примечания]

Ваш класс не является агрегатом, потому что у него есть конструктор, предоставленный пользователем.

На приведенный выше текст указывает следующий пункт в [dcl.init.list] / 3:

  • В противном случае, если T является типом класса, рассматриваются конструкторы. Применимые конструкторы перечисляются, и лучший из них выбирается путем разрешения перегрузки (13.3, 13.3.1.7).

Итак, в опубликованном C ++ 14 конструктор списка инициализаторов должен быть предпочтительнее конструктора копирования, если он совпадает. В C ++ 11 был такой же текст.


В своем вопросе вы говорите, что C ++ 14 содержит:

Если T является типом класса и список инициализаторов содержит единственный элемент типа [...]

Этого текста не было в C ++ 14, но позже он был применен в отчете о дефектах. В обновленном стандарте с примененным отчетом о дефектах (N4296) это отображается как точка маркера выше в списке точек в [dcl.init.list] / 3; так что теперь копирующий конструктор выбирается раньше в процессе, и мы не дойдем до описанного выше шага [over.match.list].

Обратите внимание, что хотя дефект называется Инициализация агрегата списком из однотипного объекта, разрешение фактически влияет на инициализацию как агрегатов, так и неагрегатов.

person M.M    schedule 29.11.2015
comment
Надеюсь, это устранение дефекта устранит все проблемы, связанные с тем, что конструкторы-копии не выбираются по назначению. - person M.M; 29.11.2015
comment
Да, я использовал стандарт N4296 с этой поправкой. Спасибо - person user3514538; 29.11.2015