Я пытаюсь понять инициализацию ссылок в С++, особенно для инициализации ссылки lvalue на ссылку «const» и rvalue.
Когда я прочитал стандартный проект здесь: https://eel.is/c++draft/dcl.init.ref#5.4.1
Если T1 или T2 является типом класса, определяемые пользователем преобразования рассматриваются с использованием правил инициализации копирования объекта типа «cv1 T1» путем пользовательского преобразования ([dcl.init], [over.match.copy] , [over.match.conv])
Я обнаружил, что когда T2 требует преобразования в T1, помимо конструкторов преобразования T1, он также позволяет использовать функции преобразования в T2.
Первоначально я думал, что это избыточно, поскольку случаи использования функций преобразования уже были рассмотрены в: https://eel.is/c++draft/dcl.init.ref#5.3.2
имеет тип класса (т. е. T2 является типом класса), где T1 не связан со ссылкой на T2 и может быть преобразован в rvalue типа «cv3 T3» или lvalue типа функции «cv3 T3», где « cv1 T1» совместим по ссылке с «cv3 T3» (см. [over.match.ref]).
Итак, мой первый вопрос, не излишне ли включать функции преобразования в 5.4.1?
Позже обнаружил, что есть небольшая разница в 5.3.2 и 5.4.1.
Например, в версии 5.3.2 T2 можно преобразовать в производный класс T1. Но в 5.4.1 временный объект создается как T1.
Поэтому я написал следующий простой код, чтобы проверить это:
#include<iostream>
class X
{
public:
virtual int get()
{
return 1;
}
};
class Y : public X
{
public:
int get()
{
return 2;
}
};
class Z
{
public:
operator const Y () const
{
return Y();
}
};
int main()
{
Z z;
X&& r = z;
std::cout << r.get() << std::endl;
return 0;
}
Вывод в одном из компиляторов:
main.cpp: In function 'int main()':
main.cpp:46:12: error: binding reference of type 'X&&' to 'const X' discards qualifiers
46 | X&& r = z;
| ^
main.cpp:40:7: note: after user-defined conversion: 'Z::operator const Y() const'
40 | operator const Y () const {return Y();}
Я ожидал, что z преобразуется во временный объект типа X, как указано в 5.4.1. Но это приводит к ошибке компилятора, говорящей, что невозможно преобразовать z в X. Если я изменил «оператор const Y» на «оператор Y», он сможет скомпилировать и напечатать 2. Но на самом деле это относится к случаю 5.3.2, а не 5.4.1, а это не то, что я хотел проверить. Хотя компилятор говорит, что он не может преобразовать z в тип X, если бы я изменил "X&& r= z;" до «X r = z;», компилятор может скомпилировать и вывести 1, что означает, что то, что он утверждает (не может преобразовать z в тип X), неверно.
Я тестировал это как на cpp.sh, так и на coliru.stacked-crooked.com.
Так что похоже, что компилятор не может реализовать в 5.4.1 случай использования функций преобразования, или я неправильно понял проект стандарта?
Редактировать:
Чтобы прояснить мой вопрос, я намеренно использую «оператор const Y» вместо «оператор Y», чтобы «const Y» не был совместим по ссылке с «cv1 T1» и не попадал в случай 5.3.2. Я ожидал, что будет использоваться 5.4.1, а временный тип X (не const X) инициализируется копированием из z. Если 5.4.1. также требуется «const X», он избыточен, как 5.3.2. Включать функции преобразования в 5.4.1 будет бессмысленно. (5.4.1. должен включать только конструктор преобразования). Поэтому я подумал, что либо компилятор неправильный (он не может обработать 5.4.1), либо стандарт является избыточным (если стандарт предполагает требовать «const X» вместо «X»). Или кто-нибудь может предоставить ситуацию, когда 5.4.1 будет использовать функции преобразования?
«Но в версии 5.4.1 временный объект создается как T1». Это неправда. 5.4.1 говорит, что результат функции преобразования используется для инициализации ссылки. Здесь не говорится, что создается временное.
@user12002570 user12002570 Это относится к случаю 5.3.2. это не то, что я хочу проверить. Я хочу протестировать 5.4.1, используя функцию преобразования. Как указано в 5.4.1, z должен быть преобразован во временный тип «cv1 T1» (в моем случае это тип X), компилятор пожаловался на «const», что означает, что он не может обработать случай в 5.4.1. Обратите внимание, что X r = z; он может скомпилироваться, что означает, что я могу использовать z для инициализации копирования типа X (который должен быть того же типа, что и временный)
@user12002570 Нет. В разделе 5 стандарта указано, что «ссылка на тип «cv1 T1» инициализируется выражением типа «cv2 T2» следующим образом», поэтому ясно, что cv T1 не включает «const».
@cppleaner В версии 5.4.1 «определяемые пользователем преобразования рассматриваются с использованием правил инициализации копирования объекта типа «cv1 T1» посредством определяемого пользователем преобразования», что создает временный объект.
пожалуйста, покажите реальный код. Код в вопросе содержит несколько опечаток и выдает множество ошибок компилятора, не связанных с вопросом.
@ 463035818_is_not_an_ai Я исправил свой тестовый код, его можно просто скопировать и вставить в любой онлайн-компилятор.
в тексте и кавычках вы можете использовать обратные кавычки для форматирования встроенного кода. Это облегчает чтение внутреннего текста code
.
@463035818_is_not_an_ai Хорошо. Я новичок в этом :)
«[...] что означает, что то, что он утверждает (невозможно преобразовать z в тип X), неверно». Это не то, что он (компилятор) говорит. В сообщении об ошибке утверждается, что он не может связать X&&
с const X
, а это уже другая история :)
@Fareanor На мой взгляд, компилятор говорит, что не может связать X&&
с const X
, потому что он использует 5.3.2 вместо 5.4.1. 5.3.2 требует, чтобы z
был преобразован в тип, совместимый по ссылке с cv T1
, но z
можно преобразовать только в const Y
, который не совместим по ссылке с X
. Вот почему я заявил, что компилятор ошибается, вместо этого ему следует использовать 5.4.1. 5 4.1 требует, чтобы X&&
был привязан к временной X
копии, инициализированной из z
. И моя формулировка означает, что объект X
действительно может быть инициализирован копированием из z
.
Я думаю, что это больше английский вопрос.
Когда в стандарте говорится, что «преобразования, определяемые пользователем, рассматриваются с использованием правил инициализации копирования», это не означает, что инициализация копирования выполняется. Вместо этого это означает, что определяемые пользователем преобразования (которые определены как «конструкторы и функции преобразования» в [class.conv]) проверяются с использованием тех же правил, что и для инициализации копирования, и наилучший из них называется .
Затем в 5.4.1 указывается, что результат вызова используется для инициализации ссылки. Это означает, что в вашем примере результат operator const Y()
напрямую используется для инициализации r
без промежуточного временного типа X
. (И эта инициализация неправильно сформирована.)
В любом случае, если вы обнаружите, что формулировка неясна или неточна, вы можете создать проблему по адресу https://github.com/cplusplus/CWG
Предположим, что стандарт намеревается напрямую использовать результат operator const Y
, этот случай уже описан в 5.3.2, так что это избыточно или какова цель включения функции преобразования в 5.4.1?
В этой области много DR, возможно, теперь безопасно использовать функции преобразования в 5.4.1.
Хотя в настоящее время struct A { operator int(); }; long&& r = A{};
обрабатывается версией 5.4.1.
Спасибо за последний пример. int
не совместим по ссылке сlong
и действительно использовал версию 5.4.1, чтобы сделать привязку возможной. Но, похоже, это также показало, что в 5.4.1 создается временный файл long
. В противном случае ссылка long
не может быть привязана к ссылке int
. Итак, мой вопрос: «Почему operator const Y
нельзя?» до сих пор нет ответа.
Программа (X &&r = z;
) имеет неверный формат, как описано ниже.
Во-первых, X &&r = z;
— это инициализация ссылки, поэтому мы переходим к dcl.init.ref:
Ссылка на тип «cv1 T1» инициализируется выражением типа «cv2 T2» следующим образом:
- [...]
- [...]
- [...]
- В противном случае T1 не должен быть связан с T2.
- Если T1 или T2 является типом класса, определяемые пользователем преобразования рассматриваются с использованием правил инициализации копирования объекта типа «cv1 T1» путем пользовательского преобразования ([dcl.init], [over.match.copy] , [over.match.conv]); программа является неправильной, если соответствующая инициализация копии, не являющаяся ссылкой, будет неправильной. Результат вызова функции преобразования, как описано для инициализации копирования без ссылки, затем используется для прямой инициализации ссылки. Для этой прямой инициализации пользовательские преобразования не учитываются.
Это означает, что результат функции преобразования, который будет иметь тип const Y
, будет использоваться для прямой инициализации r
.
Таким образом, процесс инициализации будет повторен снова. На этот раз мы сначала перейдем к прямой инициализации:
Если тип назначения является ссылочным типом, см. [dcl.init.ref].
Итак, теперь зайдите в dcl.init.ref и убедитесь, что ни один из пунктов списка не применим для инициализации ссылки r
из const Y
.
В частности, 5.1 неприменимо, поскольку ссылка не является ссылкой на lvalue.
Аналогично 5.2 не применимо.
‼5.3 тоже не применимо.
5.4 неприменимо, поскольку const Y
является ссылкой, связанной с X
, поскольку X
является основанием.
Таким образом, ни один из пунктов списка не применим для прямой инициализации r
с помощью const Y
, и поэтому X&& r = z;
имеет неправильную форму.
Но как объяснить предложение user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion
. По сути, ваш ответ означает, что временный тип X
не создается путем инициализации копирования. Вместо этого результат функции преобразования, то есть const Y
prvalue, используется для прямой инициализации ссылки X&& r
. Если это правда, значит, я неправильно понял формулировку. Я думаю, что довольно запутанно то, что в стандарте упоминается инициализация копирования объекта cv1 T1
.
В любом случае, я выбираю этот ответ, если не существует другого лучшего ответа.
@CppCoder Это означает, что инициализация ссылки имеет больше ограничений, чем инициализация копирования. То есть, если соответствующая инициализация копии имеет неправильный формат, то инициализация ссылки также будет неправильной, но если соответствующая инициализация копии сформирована правильно, это не гарантирует, что инициализация ссылки также будет правильно сформирована. Другими словами, инициализация ссылки имеет дополнительное ограничение.
@user12002570 user12002570 Значит, он используется только для выбора функции преобразования, а инициализация копирования фактически не выполняется?
Измените
X&& r = z;
наconst X&& r = z;
.