Почему я не могу инициализировать ссылку в списке инициализаторов с равномерной инициализацией?
Вот почему это:
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)
. Разве это не нарушает, равномерность равномерной инициализации?
EDIT: я пробовал с clang, и clang принимает его, так что, возможно, это ошибка GCC?
Ответы
Ответ 1
Да, его bug. Это что-то новое и было проголосовано в рабочем документе в феврале 2012 года (ссылка).
Nicol Bolas дает хорошее представление о том, что gcc на самом деле является соответствующим компилятором в соответствии со стандартом С++ 11, утвержденным FDIS, поскольку были внесены изменения в рабочий документ после этого.
Ответ 2
Я считаю, что это ошибка в компиляторе. Два абзаца, которые касаются инициализации ссылок через инициализацию списка, (в n3337):
§8.5.4/3
Список-инициализация объекта или ссылки типа T определяется следующим образом:
-
В противном случае, если в списке инициализаторов есть один элемент типа E, и либо T не является ссылочным типом, либо его ссылочный тип связан с привязкой к E, объект или ссылка инициализируются из этого элемента; если для преобразования элемента в T требуется преобразование сужения (см. ниже), программа плохо сформирована.
-
В противном случае, если T является ссылочным типом, временное значение praleue типа, на которое ссылается 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 является ссылочным типом, временное значение praleue типа, на которое ссылается T, инициализируется по списку, а ссылка привязана к этому временному.
-
В противном случае, если в списке инициализаций есть один элемент, объект или ссылка инициализируются из этого элемента; если для преобразования элемента в T требуется преобразование сужения (см. ниже), программа плохо сформирована.
Порядок в этом документе означает, что, поскольку все ссылочные типы обрабатываются первым предложением, упоминание reference в следующем абзаце не имеет смысла.
Ответ 3
(Примечание: я пишу этот ответ с двухлетней ретроспективностью с оригинального вопроса и помещаю часть информации из комментариев в фактический ответ, чтобы он был доступен для поиска).
Конечно, инициализация ссылки типа S&
с ссылкой также типа S&
должна связываться напрямую.
Проблема - это дефект в стандарте С++ 11 и был адресован DR1288. Исправленный текст появляется в С++ 14.
Комитет пояснил, что исправленный текст - это то, что было предназначено для С++ 11, и поэтому "соответствующий компилятор" должен реализовать исправленную версию.
g++ 4.8 следует опубликованному тексту стандарта С++ 11; однако, как только этот вопрос выявился, g++ 4.9 реализовала исправленную версию, даже с помощью -std=c++11
.
Обратите внимание, что проблема не ограничивается списками инициализаторов конструктора, например: S s; S &t{s};
не работает в g ++ 4.8, а также S s; S &t = s; S &u { t };