Инициализация члена ссылки const с помощью удаленного конструктора копирования

Этот код с членом const A& a в B, где A имеет удаленный конструктор копирования, не компилируется в GCC 4.8.1, но работает нормально в clang 3.4:

class A {
public:
    A() = default;
    A(const A&) = delete;
    A& operator=(const A&) = delete;
};

class B{
public:
    B(const A& a)
        : a{a}
    { }
private:
    const A& a;
};

int main()
{
    A a{};
    B b{a};
}

Кто из составителей прав?

Ошибка в GCC:

prog.cpp: In constructor ‘B::B(const A&)’:
prog.cpp:11:14: error: use of deleted function ‘A::A(const A&)’
        : a{a}
            ^
prog.cpp:4:5: error: declared here
    A(const A&) = delete;
    ^

Идея: http://ideone.com/x1CVwx


person smancill    schedule 17.07.2014    source источник
comment
Что такое Clang 5.1? Последняя версия Clang — 3.5.   -  person chris    schedule 17.07.2014
comment
В Мавериксе получаю эту Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn), поэтому и поставил 5.1, но не был уверен.   -  person smancill    schedule 17.07.2014
comment
Неудивительно, что это работает, компилятор из будущего ОООооооооооооооо   -  person Fantastic Mr Fox    schedule 17.07.2014
comment
Другое предупреждение, которое дает GCC, может быть полезно для его мыслительного процесса: предупреждение: временная привязка к 'B::a' сохраняется только до выхода из конструктора (: a{a} {)   -  person chris    schedule 17.07.2014
comment
Чтобы исправить изменение на a(a) ; форма { } требует временного   -  person M.M    schedule 17.07.2014
comment
NB. Здесь не нужно B, более простой пример: A const &z {a};   -  person M.M    schedule 17.07.2014
comment
@MattMcNabb, почему он должен создавать список инициализаторов, а не вызывать конструктор?   -  person chris    schedule 17.07.2014
comment
@Крис, какой конструктор? Ссылки не имеют конструкторов   -  person M.M    schedule 17.07.2014
comment
@MattMcNabb, эээ, извини, я думал о A, но тогда то же самое, что и int i{5};. Хотя я вижу, к чему ты клонишь. Даже при инициализации списка это все еще список инициализаторов.   -  person chris    schedule 17.07.2014
comment
Работает с gcc 4.9.0, похоже, исправлена ​​ошибка coliru.stacked-crooked.com/a/ 9ad52924a9c68ebc   -  person quantdev    schedule 17.07.2014
comment
Ahh, В противном случае, если T является ссылочным типом, временное значение prvalue типа, на которое ссылается T, инициализируется списком, и ссылка привязывается к этому временному типу. [Примечание: как обычно, привязка завершится ошибкой, и программа будет неправильно сформирована, если ссылочный тип является ссылкой lvalue на неконстантный тип. — конец примечания ]   -  person chris    schedule 17.07.2014
comment
@Joachim, извините, я сказал список инициализаторов, а не инициализацию списка. (В стандарте не используется термин равномерная инициализация). { ... } называется списком инициализаторов, даже если не требуется создавать std::initializer_list   -  person M.M    schedule 17.07.2014
comment
Дубликат этот хотя я не хочу закрывать, так как ответ Praetorian здесь хорош   -  person M.M    schedule 11.11.2014


Ответы (1)


Ваш пример можно сократить до

class A {
public:
    A() = default;
    A(const A&) = delete;
    A& operator=(const A&) = delete;
};

int main()
{
  A a{};
  A const& ar1(a); 
  A const& ar2{a}; // fails on gcc 4.8
}

Инициализация ar2 не выполняется на gcc-4.8 с ошибкой

error: use of deleted function ‘A::A(const A&)’

Он чисто компилируется на clang3.4 и gcc4.9. Это результат решения CWG по проблеме 1288.

N3337 содержит следующий язык для инициализации списка:

§8.5.4/3 [dcl.init.list]

Инициализация списка объекта или ссылки типа T определяется следующим образом:
...
— В противном случае, если T является ссылочным типом, временное значение prvalue типа, на которое ссылается T, инициализируется списком, и ссылка привязана к этому временному

Это, конечно, означает, что для инициализации ar2 требуется доступный конструктор-копия, отсюда и ошибка.


Язык изменился в N3797, где инициализация из списка инициализаторов, содержащего один элемент, имеет приоритет над случаем, указанным выше.

— В противном случае, если в списке инициализаторов есть один элемент типа E и либо T не является ссылочным типом, либо его ссылочный тип связан со ссылкой на E, объект или ссылка инициализируется из этого элемента; ...
— В противном случае, если T является ссылочным типом, временное значение prvalue типа, на которое ссылается T, инициализируется списком копирования или инициализируется прямым списком, в зависимости от типа инициализации ссылки и ссылки. привязан к этому временному.

Таким образом, gcc 4.9 и clang 3.4 реализуют решение проблемы 1288, а gcc 4.8 следует формулировке стандарта C++11.

person Praetorian    schedule 17.07.2014
comment
отчет об ошибке gcc, подтверждающий, что это изменение было сделано для gcc4.9. - person Praetorian; 17.07.2014
comment
Итак, если код должен поддерживать C++11, он должен использовать ar2(a), верно? Является ли решение проблемы 1288 частью C++11 или C++14? - person smancill; 17.07.2014
comment
@smancill Хороший вопрос. Интересно, что в этой ситуации не допускается копирование-элизион; если конструктор-копия не удален и имеет побочные эффекты, то это исправление ошибки будет критическим изменением. - person M.M; 17.07.2014
comment
@smancill Да, использование круглых скобок вместо фигурных скобок решает проблему. И я действительно не знаю ответа на ваш второй вопрос. Очевидно, что разрешение было добавлено после стандарта C++11, но вы получите исправление бесплатно с -std=c++11, если обновитесь до gcc4.9. - person Praetorian; 17.07.2014
comment
@MattMcNabb Побочные эффекты в конструкторах копирования - настоящая ошибка: P - person Praetorian; 17.07.2014