Синтаксис для универсальных ссылок
Это ссылка на значение:
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
(шучу над последним)? Если да, то каковы причины отказа от альтернативных синтаксисов?
3 ответа
Универсальная ссылка, такая как 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&
здесь было бы нелогичным. Таким образом, мы должны добавить "специальное правило вычета, чтобы отключить специальное правило вычета" в этом случае:-(
Как разработчик с опытом работы в C++ всего несколько лет, я должен согласиться с тем, что универсальные ссылки сбивают с толку. Мне кажется, что это совершенно невозможно понять, не говоря уже о том, чтобы активно использовать, не читая Скотта Мейерса и / или не просматривая соответствующие выступления на YouTube.
Дело не только в том, что && может быть ссылкой на r-значение или «универсальной ссылкой», это способ использования шаблонов для различения типов ссылок. Представьте, что вы нормальный инженер, занимающийся разработкой программного обеспечения. Вы читаете что-то вроде:
template <typename T>
void foo (T&& whatever) {...}
а затем тело функции foo сообщает вам, что входной параметр должен быть очень специфическим типом класса, который был определен во внутренней библиотеке. Замечательно, как вы думаете, шаблон устарел в текущей версии программного обеспечения, вероятно, это пережиток предыдущих разработок, так что давайте избавимся от него.
Удачи с ошибками компилятора ...
Никто, кто не усвоил это явно, не предположил бы, что вы реализуете здесь foo как шаблон функции только для того, чтобы использовать правила вывода шаблона и правила сворачивания ссылок, которые совпадают, так что вы в конечном итоге получите то, что называется «идеальной пересылкой». Это действительно больше похоже на грязный взлом, чем на что-либо другое. Я думаю, что создание синтаксиса для универсальных ссылок упростило бы задачу, и разработчик, который раньше работал над более старой базой кода, по крайней мере знал бы, что здесь есть что-то, что нужно гуглить. Всего два цента.