Инициализация списка ссылки: правильно ли 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, объектом или < strong> ссылка инициализируется из этого элемента (путем инициализации копирования для инициализации списка копирования или путем прямой инициализации для инициализации прямого списка); если для преобразования элемента в T требуется сужающее преобразование (см. ниже), программа имеет неправильный формат.

В противном случае, если T является ссылочным типом, создается prvalue типа, на который ссылается T. prvalue инициализирует свой объект результата путем инициализации списка-копирования или инициализации прямого списка, в зависимости от типа инициализации для справки. Затем 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]) и выбор лучшего из них с помощью разрешения перегрузки),
...
тогда ссылка привязывается к результату преобразования ... value

...

В противном случае, если выражение инициализатора
...
имеет тип класса (т. Е. 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), но язык инициализации ссылок и взаимодействия с инициализацией списка очень нечеткий.


person Mike Lui    schedule 09.01.2019    source источник


Ответы (1)


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

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

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

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

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

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

Для заданных типов «cv1 T1» и «cv2 T2», «cv1 T1» связано со ссылкой на « cv2 T2 ”, если T1 имеет тот же тип, что и T2, или T1 является базовым классом T2.

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

person Brian Bi    schedule 09.01.2019