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

Это ссылка на значение:

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 как шаблон функции только для того, чтобы использовать правила вывода шаблона и правила сворачивания ссылок, которые совпадают, так что вы в конечном итоге получите то, что называется «идеальной пересылкой». Это действительно больше похоже на грязный взлом, чем на что-либо другое. Я думаю, что создание синтаксиса для универсальных ссылок упростило бы задачу, и разработчик, который раньше работал над более старой базой кода, по крайней мере знал бы, что здесь есть что-то, что нужно гуглить. Всего два цента.

Другие вопросы по тегам