Почему T const && не является ссылкой для пересылки?
В контексте шаблона применяются следующие правила "сбрасывания ссылок":
template <typename T>
void foo(T && t)
{
//T& & -> T&
//T& && -> T&
//T&& & -> T&
//T&& && -> T&&
}
Почему язык запрещает "универсальные ссылки" от наличия квалификаторов const
?
template <typename T>
void foo(T const && t)
Казалось бы, имеет смысл, если тип разрешил ссылку (3 из 4 случаев).
Я уверен, что эта идея несовместима с каким-то другим аспектом дизайна языка, но я не вижу полной картины.
Ответы
Ответ 1
Первоначально в справочном предложении rvalue говорилось, что преобразование происходит, если P
является "ссылочным типом rvalue". Однако отчет о дефекте позже заметил
Кроме того, рассмотрим этот случай:
template <class T> void f(const T&&);
...
int i;
f(i);
Если в этом случае вывести T
как int&
, тогда f(i)
вызывает f<int&>(int&)
, что кажется противоречивым. Мы предпочитаем называть f<int>(const int&&)
. Поэтому мы хотели бы, чтобы формулировка уточнила, что правило вычитания A&
в пункте 14.8.2.1 [temp.deduct.call] применяется только к форме T&&
, а не к cv T&&
, как подразумевает в настоящее время примечание.
Кажется, что был период времени, когда const T &&
, при T
, являющемся U&
, был преобразован в const U&
. Это было изменено, чтобы соответствовать другому правилу, в котором говорится, что const T
, где T
есть U&
, останется U&
(cv-квалификаторы по ссылкам игнорируются). Итак, когда вы выведете T
в приведенном выше примере на int&
, параметр функции останется int&
, а не const int&
.
В отчете о дефекте репортер заявляет: "Мы предпочитаем, чтобы f<int>(const int&&)
назывался", однако не дает никаких оснований в отчете о дефектах. Я могу себе представить, что причина заключалась в том, что казалось слишком сложным исправить это, не введя несогласованность с другими правилами.
Мы также должны иметь в виду, что отчет о дефектах был сделан в то время, когда ссылки rvalue все равно могли привязываться к lvalues - i.e const int&&
мог привязываться к int lvalue. Это было запрещено только позже, когда появилась статья Дейва и Дага "Проблема безопасности с ссылками на RValue". Итак, мне кажется, что вычет, который работает (в то время), стоил больше, чем вычет, который просто противоречил интуитивным и отброшенным квалификаторам.
Ответ 2
Это уже происходит для ссылок; если ваш T
равен U const &
, тогда T &&
рухнет до U const &
. Термин "универсальная ссылка" действительно означает универсальную ссылку: вам не нужно указывать const
, чтобы получить постоянную ссылку.
Если вы хотите иметь поистине универсальный механизм ссылок, вам нужно, чтобы ваш T &&
мог стать всевозможными ссылками, будет иметь все виды констант. И T &&
делает именно это. Он сворачивается во все четыре случая: и ссылки l и r-значения, и оба const
и не const
.
Объясненный другим способом, const
ness является атрибутом типа, а не ссылкой, т.е. когда вы говорите T const &
, вы на самом деле говорите о U &
, где U
- T const
. То же самое верно для &&
(хотя ссылка r-значения на const
менее полезна).
Это означает, что если вы хотите, чтобы ваша универсальная ссылка сворачивалась на U const &
, просто передайте ей то, что вам нужно: a U const &
, и он скроется до этого.
Чтобы ответить на ваш вопрос более прямо: язык не "запрещает" использование const
в декларации универсальной ссылки, за sé. Он говорит, что если вы измените механизм объявления универсальной ссылки даже немного - даже вставив низко const
между T
и &&
- тогда у вас не будет (буквально) "универсального" ссылка больше, потому что она просто не примет ничего и всего.
Ответ 3
Почему, по вашему мнению, язык не позволяет ссылаться на ссылки на const r-value?
В следующем коде, что будет напечатано?
#include <iostream>
struct Foo
{
void bar() const &
{
std::cout << "&\n";
}
void bar() const &&
{
std::cout << "&&\n";
}
};
const Foo make() {
return Foo{};
}
int main()
{
make().bar();
}
Ответ:
&&
почему? Поскольку make() возвращает объект const, и в этом контексте он является временным. Поэтому ссылка r-значения на const.
Ответ 4
Вывод аргумента шаблона имеет специальный случай для "ссылки rvalue на cv-unqualified параметры шаблона". Именно этот особый случай основан на пересылке/универсальных ссылках. Подробнее см. Раздел "Вычитание из вызова функции" в связанной статье.
Обратите внимание, что до вычитания аргумента шаблона все cv-квалификаторы верхнего уровня удаляются; однако ссылки никогда не имеют квалификаторов cv-верхнего уровня и выше правило не применяется, поэтому специальное правило также не применяется. (В отличие от указателей нет "ссылки на константу", только "ссылка на const" )