Синтаксис для универсальных ссылок

Это ссылка на rvalue:

void foo(int&& a);

Он не привязывается к lvalues:

int i = 42;
foo(i);   // error

Это универсальная ссылка:

template<typename T>
void bar(T&& b);

Он связывается с rvalues, а также привязывается к lvalues:

bar(i);   // okay

Это ссылка на rvalue:

template<typename T>
struct X
{
    void baz(T&& c);
};

Он не привязывается к lvalues:

X<int> x;
x.baz(i);   // error

Почему универсальные ссылки используют тот же синтаксис, что и ссылки rvalue? Разве это не лишний источник путаницы? Был ли комитет когда-либо рассматривал альтернативные синтаксисы, такие как T&&&, T&*, [email protected] или T&42 (просто шутите на последнем)? Если да, то в чем причины отказа от альтернативных синтаксисов?

Ответ 1

Универсальная ссылка, такая как T&&, может выводить T как "тип объекта" или "ссылочный тип"

В вашем примере он может выводить T как int при передаче rvalue, поэтому параметр функции int&& или он может выводить T как int& при передаче lvalue, и в этом случае параметр функции int& (поскольку правила сбрасывания ссылок говорят, что std::add_rvalue_reference<int&>::type - это просто int&)

Если T не выводится вызовом функции (как в примере X::baz), то его невозможно вывести на int&, поэтому ссылка не является универсальной ссылкой.

Таким образом, IMHO действительно не нуждается в новом синтаксисе, он прекрасно вписывается в вычет аргумента шаблона и правила обращения с кодами, с небольшой настройкой, которую параметр шаблона можно вывести как ссылочный тип (где в С++ 03 используется шаблон функции параметр типа T или T& всегда выводит T как тип объекта.)

Эти семантики и этот синтаксис были предложены с самого начала, когда в качестве решения проблемы переадресации были предложены ссылки rvalue и настройка правил вывода аргументов, см. N1385. Использование этого синтаксиса для обеспечения идеальной пересылки было предложено параллельно с предложением ссылок rvalue для целей семантики перемещения: N1377 находилась в той же рассылке, что и N1385. Я не думаю, что альтернативный синтаксис никогда всерьез не предлагался.

ИМХО альтернативный синтаксис на самом деле будет более запутанным в любом случае. Если у вас был template<typename T> void bar(T&@) как синтаксис универсальной ссылки, но та же семантика, что и у нас сегодня, то при вызове bar(i) параметр шаблона T может быть выведен как int& или int, а параметр функции будет иметь тип int& или int&&... ни один из которых не является "T&@" (независимо от того, что этот тип.) Таким образом, у вас будет грамматика на языке для декларатора T&@, который не является типом который может когда-либо существовать, потому что он на самом деле всегда ссылается на какой-то другой тип: int& или int&&.

По крайней мере, с синтаксисом, который у нас есть, тип T&& является реальным типом, а правила сворачивания ссылок не являются специфичными для функциональных шаблонов с использованием универсальных ссылок, они полностью согласуются с остальной системой типа вне шаблонов:

struct A {} a;
typedef A& T;
T&& ref = a;    // T&& == A&

Или эквивалентно:

struct A {} a;
typedef A& T;
std::add_rvalue_reference<T>::type ref = a;    // type == A&

Если T - ссылочный тип lvalue, T&& тоже. Я не думаю, что нужен новый синтаксис, правила действительно не настолько сложны или запутываются.

Ответ 2

Почему универсальные ссылки используют тот же синтаксис, что и ссылки rvalue? Разве это не лишний источник путаницы? Был ли комитет когда-либо рассмотрен альтернативный синтаксис...

Да, это запутанно, ИМО (я не соглашусь с @JonathanWakely здесь). Я помню, что во время неофициального обсуждения (обед, я думаю) о раннем дизайне общей функции мы обсуждали разные обозначения (Howard Hinnant и Dave Abrahams были там, приносящие свои идеи, и ребята EDG давали отзывы о том, как это может быть на основном языке, что предшествует N1377). Я думаю, что я помню &? и &|&&, но все это было словесным; Я не знаю о встречах, которые были сделаны (но я считаю, что это также, когда Джон предложил использовать && для ссылок rvalue). Тем не менее, это были ранние этапы разработки, и в то время было много фундаментальных семантических вопросов. (Например, во время того же обеденного обсуждения мы также подняли возможность не иметь двух видов ссылок, но вместо этого имели два типа эталонных параметров.)

Более поздний аспект путаницы этих причин встречается в функции С++ 17 "вывод аргумента шаблона шаблона" (P0099R3). Там подпись шаблона функции формируется путем преобразования подписи конструкторов и шаблонов конструктора. Для чего-то вроде:

template<typename T> struct S {
  S(T&&);
};

подпись шаблона функции

template<typename T> auto S(T&&)->S<T>;

формируется для использования для вывода декларации типа

int i = 42;
S s = i;  // Deduce S<int> or S<int&>?

Выведение T = int& здесь было бы контр-интуитивным. Поэтому нам нужно добавить "специальное правило вычета, чтобы отключить правило специального вычета" в этом случае: - (