Список инициализации ссылки: правильно ли GCC или Clang?

Учитывая этот пример:

int g_i = 10;
struct S {
    operator int&(){ return g_i; }
};

int main() {
    S s;
    int& iref1 = s; // implicit conversion

    int& iref2 = {s}; // clang++ error, g++ compiles fine:
                      // `s` is converted
                      // to a temporary int and binds with
                      // lvalue reference

    int&& iref3 = {s}; // clang++ compiles, g++ error:
                       // cannot bind rvalue reference
                       // to lvalue
}

Ошибки, как описано в комментариях.
gcc 8.2.1 и clang 7.0.1 были использованы и не согласны с тем, что происходит в этом примере. Может ли кто-нибудь уточнить это?

В инициализации списка:

В противном случае, если список инициализатора имеет единственный элемент типа E и либо T не является ссылочным типом, либо его ссылочный тип связан со ссылкой на E, объект или ссылка инициализируются из этого элемента (путем копирования-инициализации для copy-list -инициализация или прямая инициализация для прямой инициализации списка); если для преобразования элемента в T требуется сужающее преобразование (см. ниже), программа является некорректной.

В противном случае, если T является ссылочным типом, генерируется значение типа, на который ссылается T. Значение prvalue инициализирует свой объект результата путем инициализации copy-list-initialization или direct-list-initialization, в зависимости от вида инициализации для ссылки. Затем значение prvalue используется для прямой инициализации ссылки. [Примечание: Как обычно, привязка завершится неудачно, и программа будет неправильно сформирована, если ссылочный тип является lvalue-ссылкой на неконстантный тип. - конец примечания]

В справочной инициализации:

Для данных типов "cv1 T1" и "cv2 T2" "cv1 T1" связан со ссылкой на "cv2 T2", если T1 относится к тому же типу, что и T2, или T1 является базовым классом для T2. "Cv1 T1" совместим с "cv2 T2", если
- T1 связан с T2 или
- T2 - это "функция без исключения", а T1 - это "функция", где типы функций в остальном одинаковы,

... а позже есть некоторые (лично неоднозначные) языки для пользовательских преобразований:

Например:

Если ссылка является ссылкой lvalue и выражением инициализатора
...
имеет тип класса (т. е. T2 является типом класса), где T1 не связан со ссылкой на T2 и может быть преобразован в lvalue типа "cv3 T3", где "cv1 T1" совместим со ссылкой с "cv3" T3” (это преобразование выбирается путем перечисления применимых функций преобразования ([over.match.ref]) и выбора наилучшего с помощью разрешения перегрузки),
...
тогда ссылка привязывается к... значению результата преобразования

...

В противном случае, если выражение инициализатора
...
имеет тип класса (т. е. T2 является типом класса), где T1 не связан со ссылкой на T2 и может быть преобразован в rvalue или функцию lvalue типа "cv3 T3", где "cv1 T1" совместим со ссылками с "cv3 T3"
... тогда значение результата... преобразования во втором случае называется преобразованным инициализатором. Если преобразованный инициализатор является prvalue, его тип T4 настраивается на тип "cv1 T4"

...

Иначе:
- Если T1 или T2 является типом класса, а T1 не связан со ссылкой на T2, пользовательские преобразования рассматриваются с использованием правил для инициализации копирования объекта типа "cv1 T1" путем пользовательского преобразования... результат вызова функции преобразования, как описано для инициализации копирования без ссылки, затем используется для прямой инициализации ссылки. Для этой прямой инициализации пользовательские преобразования не рассматриваются.

...

В противном случае выражение инициализатора неявно преобразуется в значение типа "cv1 T1". Применяется временное преобразование материализации, и ссылка привязывается к результату.

Эти правила довольно тонкие, и я не могу полностью понять каждую ситуацию. Мне кажется, что должно генерироваться prvalue (я согласен с clang), но язык инициализации ссылок и взаимодействия с инициализацией списка очень размыт.

1 ответ

Решение

Давайте прочитаем стандарт в правильном порядке, чтобы мы знали, какие разделы относятся к рассматриваемой ситуации.

[dcl.init] / 17 говорит:

Семантика инициализаторов следующая... Если инициализатор представляет собой (не заключенный в скобки) фигурный список инициализации или является = braced-init-list, объект или ссылка инициализируются списком (11.6.4) ...

Итак, мы идем в [dcl.init.list] (11.6.4). Пункт 3 гласит:

Инициализация списка объекта или ссылки типа T определяется следующим образом: (... случаи, которые не применяются, исключаются из этой цитаты...) В противном случае, если список инициализаторов имеет один элемент типа E и либо T не является ссылочным типом или его ссылочный тип связан со ссылкой на E... иначе, если T является ссылочным типом, prvalue типа, на который ссылается T генерируется. Значение prvalue инициализирует свой объект результата путем инициализации copy-list-initialization или direct-list-initialization, в зависимости от вида инициализации для ссылки. Затем значение prvalue используется для прямой инициализации ссылки. [ Примечание: Как обычно, привязка завершится неудачно, и программа будет неправильно сформирована, если ссылочный тип является lvalue-ссылкой на неконстантный тип. —Конечная записка ]

Согласно [dcl.init.ref]/4:

Даны типы "cv1" T1"И"cv2 T2","Cv1 T1Относится к "cv2 T2" если T1 тот же тип, что и T2, или же T1 это базовый класс T2,

Поэтому в вашем коде ссылочный тип int не связан со ссылкой на тип в списке инициализатора, а именно S, Таким образом, [dcl.init.list]/3, значение типа int генерируется, и принимает форму int{s}, И, как говорится в записке, в случае iref2, программа плохо сформирована, потому что она пытается привязать неконстантную ссылку lvalue к prvalue. В случае iref3программа должна компилироваться, т.к. iref3 привязан к предварительному результату int{s},

Другие вопросы по тегам