Инициализация списка ссылки: правильно ли 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 и лязг 7.0.1 использовались и расходятся во мнениях относительно того, что происходит в этом примере. Может кто-нибудь прояснить это?

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

Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization); if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.

Otherwise, if T is a reference type, a prvalue of the type referenced by T is generated. The prvalue initializes its result object by copy-list-initialization or direct-list-initialization, depending on the kind of initialization for the reference. The prvalue is then used to direct-initialize the reference. [ Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type. — end note ]

При инициализации ссылки:

Given types “cv1 T1” and “cv2 T2”, “cv1 T1” is reference-related to “cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2. “cv1 T1” is reference-compatible with “cv2 T2” if
- T1 is reference-related to T2, or
- T2 is “noexcept function” and T1 is “function”, where the function types are otherwise the same,

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

Например:

If the reference is an lvalue reference and the initializer expression
...
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions ([over.match.ref]) and choosing the best one through overload resolution),
...
then the reference is bound to the ... value result of the conversion

...

Otherwise, if the initializer expression
...
has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an rvalue or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3”
... then the value of the ... result of the conversion in the second case is called the converted initializer. If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4”

...

Otherwise:
- If T1 or T2 is a class type and T1 is not reference-related to T2, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion ... The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference. For this direct-initialization, user-defined conversions are not considered.

...

Otherwise, the initializer expression is implicitly converted to a prvalue of type “cv1 T1”. The temporary materialization conversion is applied and the reference is bound to the result.

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
11
0
211
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

The semantics of initializers are as follows... If the initializer is a (non-parenthesized) braced-init-list or is =braced-init-list, the object or reference is list-initialized (11.6.4) ...

Итак, мы переходим к [dcl.init.list] (11.6.4). В пункте 3 говорится:

List-initialization of an object or reference of type T is defined as follows: (... cases that don't apply are elided from this quotation...) Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E ... otherwise, if T is a reference type, a prvalue of the type referenced by T is generated. The prvalue initializes its result object by copy-list-initialization or direct-list-initialization, depending on the kind of initialization for the reference. The prvalue is then used to direct-initialize the reference. [ Note: As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type. —end note ]

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

Given types “cv1T1” and “cv2T2”, “cv1T1” is reference-related to “cv2T2” if T1 is the same type as T2, or T1 is a base class of T2.

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

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