Почему я не могу инициализировать ссылку в списке инициализаторов с равномерной инициализацией?
Вот почему это:
struct S {};
struct T
{
T(S& s) : s{s} {}
S& s;
};
int main()
{
S s;
T t{s};
}
дай мне ошибку компилятора с GCC 4.7:
test.cpp: In constructor 'T::T(S&)':
test.cpp:5:18: error: invalid initialization of non-const reference of type 'S&' from an rvalue of type '<brace-enclosed initializer list>'
?
Чтобы исправить ошибку, я должен изменить s{s}
в s(s)
, Разве это не нарушает равномерность равномерной инициализации?
РЕДАКТИРОВАТЬ: Я пытался с Clang, и Clang принимает это, так что, возможно, это ошибка GCC?
3 ответа
Да, это ошибка. Это что-то новое, и за него проголосовал рабочий документ в феврале 2012 года ( ссылка).
Никол Болас отмечает, что gcc на самом деле является соответствующим компилятором в соответствии со стандартом C++11, утвержденным FDIS, потому что после этого были внесены изменения в рабочий документ.
Я считаю, что это ошибка в компиляторе. Два параграфа, которые имеют дело с инициализацией ссылки посредством инициализации списка, (в n3337):
§8.5.4 / 3
Инициализация списка объекта или ссылки типа T определяется следующим образом:
В противном случае, если список инициализатора имеет единственный элемент типа E и либо T не является ссылочным типом, либо его ссылочный тип связан со ссылкой на E, объект или ссылка инициализируются из этого элемента; если для преобразования элемента в T требуется сужающее преобразование (см. ниже), программа является некорректной.
В противном случае, если T является ссылочным типом, предварительное значение временного типа, на которое ссылается T, инициализируется списком, и ссылка привязывается к этому временному объекту. [Примечание: Как обычно, привязка завершится неудачно, и программа будет некорректной, если ссылочный тип является lvalue-ссылкой на неконстантный тип. - конец примечания]
Компилятор, кажется, применяет последний абзац, когда он должен применять первый, так как связанный со ссылкой определяется как
8.5.3/4
Для заданных типов "cv1 T1" и "cv2 T2" "cv1 T1" связан со ссылкой на "cv2 T2", если T1 имеет тот же тип, что и T2, или T1 является базовым классом T2.
В случае вопроса типы ссылки и инициализатора внутри списка фигурных скобок одинаковы, что означает, что инициализация должна быть действительной.
В проекте FDIS в эквивалентных пунктах порядок был обратным. Это означает, что черновик FDIS (n3290) не позволял инициализировать скобками список *lvalue*s. С другой стороны, при чтении текста кажется очевидным, что это ошибка в стандарте и что намерение имело порядок n3337:
В противном случае, если T является ссылочным типом, предварительное значение временного типа, на которое ссылается T, инициализируется списком, и ссылка привязывается к этому временному объекту.
В противном случае, если список инициализаторов имеет один элемент, объект или ссылка инициализируются из этого элемента; если для преобразования элемента в T требуется сужающее преобразование (см. ниже), программа является некорректной.
Порядок в этом документе означает, что, поскольку все ссылочные типы обрабатываются первым предложением, упоминание ссылки в следующем абзаце не имеет смысла.
(Примечание: я пишу этот ответ с пользой 2 года назад с момента первоначального вопроса; и помещаю часть информации из комментариев в реальный ответ, чтобы ее можно было найти).
Конечно, инициализация ссылки типа S&
со ссылкой также типа S&
должен связывать напрямую.
Проблема является дефектом в стандарте C++11, который был устранен DR1288. Исправленный текст появляется в C++14.
Комитет пояснил, что исправленный текст - это то, что предназначалось для C++11, и поэтому "соответствующий компилятор" должен реализовать исправленную версию.
g++ 4.8 следовал опубликованному тексту стандарта C++11; однако, как только эта проблема появилась, g++ 4.9 реализовал исправленную версию, даже с -std=c++11
переключатель.
Обратите внимание, что проблема не ограничивается списками инициализатора конструктора, например: S s; S &t{s};
не работает в g ++ 4.8 и не работает S s; S &t = s; S &u { t };