Почему 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" )