Это ссылка на значение:
void foo(int&& a);
Это не связывает с lvalues:
int i = 42;
foo(i); // error
Это универсальная ссылка:
template<typename T>
void bar(T&& b);
Он связывается с rvalues, а также с lvalues:
bar(i); // okay
Это ссылка на значение:
template<typename T>
struct X
{
void baz(T&& c);
};
Это не связывает с lvalues:
X<int> x;
x.baz(i); // error
Почему универсальные ссылки используют тот же синтаксис, что и ссылки rvalue? Разве это не ненужный источник путаницы? Рассматривал ли комитет когда-нибудь альтернативные синтаксисы, такие как T&&&
, T&*
, T@
или же T&42
(шучу над последним)? Если да, то каковы причины отказа от альтернативных синтаксисов?
Универсальная ссылка, такая как T&&
может вывести T
быть «тип объекта«или»тип ссылки«
В вашем примере это может вывести T
как int
когда передано значение, поэтому параметр функции int&&
или это может вывести T
как int&
когда передано lvalue, в этом случае параметр функции int&
(потому что ссылки сворачиваются правила говорят std::add_rvalue_reference<int&>::type
просто int&
)
Если T
не выводится вызовом функции (как в вашем X::baz
пример) тогда это не может быть выведено int&
Таким образом, ссылка не является универсальной ссылкой.
Так что IMHO действительно нет необходимости в новом синтаксисе, он хорошо вписывается в правила вывода шаблонов и свертывания ссылок, с небольшой настройкой, что параметр шаблона может быть выведен как ссылочный тип (где в C ++ 03 параметр шаблона функции типа T
или же T&
всегда выводит T
как тип объекта.)
Эта семантика и этот синтаксис были предложены с самого начала, когда в качестве решения проблемы пересылки были предложены ссылки на значения и настройка правил вывода аргументов, см. 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&&
слишком. Я не думаю, что нужен новый синтаксис, правила на самом деле не так уж сложны или запутаны.
Почему универсальные ссылки используют тот же синтаксис, что и ссылки rvalue? Разве это не ненужный источник путаницы? Комитет когда-нибудь рассматривал альтернативные синтаксисы …
Да, это сбивает с толку, ИМО (я не согласен с @JonathanWakely здесь). Я помню, что во время неформальной дискуссии (я думаю, за обедом) о раннем дизайне общей функции мы обсуждали разные нотации (Говард Хиннант и Дейв Абрахамс предлагали свою идею, а ребята из EDG давали отзывы о том, как она могла бы соответствовать на базовом языке, это предшествовало N1377). Я думаю, что я помню &?
а также &|&&
были рассмотрены, но все это было словесным; Я не знаю, были ли сделаны заметки о заседаниях (но я полагаю, что это также, когда Джон предложил использовать &&
для справки). Однако это были ранние этапы проектирования, и в то время было много фундаментальных семантических проблем, которые нужно было рассмотреть. (Например, во время того же обеденного обсуждения мы также подняли вопрос о возможности не иметь двух видов ссылок, а вместо этого иметь два типа ссылочных параметров.)
Более свежий аспект этой путаницы обнаружен в функции C ++ 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&
здесь было бы нелогичным. Таким образом, мы должны добавить «специальное правило вычета, чтобы отключить специальное правило вычета» в этом случае 🙁