Запрошенное краткое объяснение правил свертывания ссылок: (1) A& & -> A&, (2) A& && -> A&, (3) A&& & -> A&, и (4) A&& && -> A&&

Следующая ссылка предоставляет 4 формы свертывания ссылок (если я правильно понял, что это только 4 формы): http://thbecker.net/articles/rvalue_references/section_08.html.

По ссылке:

  1. A & & становится A &
  2. A & && становится A &
  3. A && & становится A &
  4. A && && становится A &&

Хотя я могу сделать обоснованное предположение, я хотел бы получить краткое объяснение обоснования каждого из этих правил свертывания ссылок.

Смежный вопрос, если можно так: эти правила свертывания ссылок используются в C++11 внутри таких утилит STL, как std::move(), std::forward()и тому подобное, в типичных реальных случаях использования? (Примечание: я специально спрашиваю, используются ли правила свертывания ссылок в C++11, в отличие от C++03 или более ранних.)

Я задаю этот связанный вопрос, потому что мне известны такие утилиты C++11, как std::remove_reference, но я не знаю, если ссылки, связанные утилиты, такие как std::remove_reference обычно используются в C++11, чтобы избежать необходимости в правилах свертывания ссылок или в сочетании с правилами свертывания ссылок.

3 ответа

Решение

Ссылка свернутая правила (за исключением A& & -> A&, который является C++98/03) существует по одной причине: чтобы совершенная пересылка работала.

"Совершенная" пересылка означает эффективную пересылку параметров, как если бы пользователь вызвал функцию напрямую (за вычетом исключения, которое нарушается при пересылке). Пользователь может передать три типа значений: lvalues, xvalues ​​и prvalues, и есть три способа, которыми получающее местоположение может принимать значение: по значению, по (возможно, const) lvalue ссылке и (возможно, const) rvalue ссылка.

Рассмотрим эту функцию:

template<class T>
void Fwd(T &&v) { Call(std::forward<T>(v)); }

По значению

Если Call принимает его параметр по значению, тогда в этот параметр должно произойти копирование / перемещение. Какой из них зависит от того, что является входящим значением. Если входящее значение является lvalue, то оно должно скопировать lvalue. Если входящее значение является rvalue (которые вместе являются xvalues ​​и prvalues), то оно должно от него отказаться.

Если вы позвоните Fwd с lvalue, правила вывода типов в C++ означают, что T будет выведено как Type&, где Type это тип lvalue. Очевидно, что если lvalue const, будет выведено как const Type&, Правила свертывания ссылок означают, что Type & && становится Type & за vlvalue ссылка. Это именно то, что нам нужно назвать Call, Вызов его со ссылкой на lvalue приведет к принудительному копированию, как если бы мы вызывали его напрямую.

Если вы позвоните Fwd со значением r (то есть: Type временное выражение или определенный Type&& выражения), то T будет выведено как Type, Ссылка свернутых правил дает нам Type &&, который провоцирует перемещение / копирование, что почти так же, как если бы мы вызывали его напрямую (минус исключение).

По ссылке

Если Call принимает значение по ссылке lvalue, тогда оно должно вызываться только тогда, когда пользователь использует параметры lvalue. Если это ссылка на const-lvalue, то она может быть вызвана чем угодно (lvalue, xvalue, prvalue).

Если вы позвоните Fwd с lvalue, мы снова получаем Type& как тип v, Это будет привязано к неконстантной lvalue ссылке. Если мы называем это const lvalue, мы получаем const Type&, который будет привязан только к ссылочному аргументу const lvalue в Call,

Если вы позвоните Fwd с xvalue, мы снова получаем Type&& как тип v, Это не позволит вам вызывать функцию, которая принимает неконстантное lvalue, так как xvalue не может связываться с неконстантной ссылкой lvalue. Может связываться с константной ссылкой на значение, поэтому если Call использовал const&мы могли бы позвонить Fwd с xvalue.

Если вы позвоните Fwd с prvalue, мы снова получаем Type&&так что все работает как раньше. Вы не можете передать временное значение функции, которая принимает неконстантное lvalue, поэтому наша функция пересылки также захлебнется при попытке сделать это.

По ссылке

Если Call принимает значение по ссылке rvalue, тогда оно должно вызываться только тогда, когда пользователь использует параметры xvalue или rvalue.

Если вы позвоните Fwd с lvalue, мы получаем Type&, Это не будет привязываться к ссылочному параметру rvalue, поэтому возникает ошибка компиляции. const Type& также не будет привязываться к ссылочному параметру rvalue, поэтому он все равно не работает. И это именно то, что случилось бы, если бы мы позвонили Call прямо с lvalue.

Если вы позвоните Fwd с xvalue, мы получаем Type&&, который работает (cv-квалификация по-прежнему имеет значение, конечно).

То же самое касается использования prvalue.

станд:: вперед

Сам std::forward использует правила свертывания ссылок аналогичным образом, чтобы передать входящие ссылки на rvalue как xvalues ​​(функция возвращает значения, которые Type&& xvalues) и входящие ссылки lvalue как lvalues ​​(возвращая Type&).

Правила на самом деле довольно просты. Rvalue reference является ссылкой на какое-то временное значение, которое не сохраняется за пределами выражения, которое его использует - в отличие от lvalue reference который ссылается на постоянные данные. Поэтому, если у вас есть ссылка на постоянные данные, независимо от того, с какими другими ссылками вы их объединяете, фактические ссылочные данные являются lvalue - это относится к первым 3 правилам. Четвертое правило также является естественным - ссылка на значение на ссылку на значение по-прежнему является ссылкой на непостоянные данные, поэтому ссылка на значение присваивается.

Да, утилиты C++11 опираются на эти правила, реализация, обеспеченная вашей ссылкой, соответствует реальным заголовкам: http://en.cppreference.com/w/cpp/utility/forward

И да, правила свертывания вместе с правилом вывода аргументов шаблона применяются при использовании std::move а также std::forward утилиты, как описано в вашей ссылке.

Использование черт типа, таких как remove_reference действительно зависит от ваших потребностей; move а также forward чехол для самых случайных случаев.

Здесь нуб в C++. Я просто пытаюсь поделиться своим коллективным пониманием. Обоснование правил свертывания ссылок волновало меня в течение довольно долгого времени, но я чувствую, что кое-что разрешилось теперь, основываясь на следующем потоке мыслей.

Это перспективный взгляд на ответ SomeWittyUsername . Мой ход мыслей по обоснованию правил свертывания ссылок представлен в виде постепенно расширяющегося списка маркеров.

  • Ссылки (lvalue и rvalue) можно рассматривать как псевдонимы дескрипторов объектов.
  • Ссылки Rvalue также отмечают свойство объекта, из которого его можно переместить.
  • В случае ссылки на ссылку первый дескриптор ссылки можно рассматривать как дескриптор, с которым передается объект, а второй — как дескриптор, с которым он принимается в качестве параметра.
  • Первую ссылку в ссылке на ссылку можно рассматривать как ту, которая сообщает нам, что разрешено для объекта.
    • Это ссылка, которая обеспечивается параметром типа шаблона.
  • Вторую ссылку в ссылке на ссылку можно рассматривать как ту, которая сообщает нам, что параметр собирается с ней делать.
    • Это ссылка, которую обеспечивает параметр функции шаблона.
  • В комбинациях, где либо подвижность исходного объекта не разрешена (T& &&), либо параметр не собирается перемещаться из него (T&& &) , либо и то, и другое (T& &) , компилятор рассматривает его как неподвижный из-за этого конкретного объекта. Применение
  • Только в комбинации, где исходный объект можно перемещать, а параметр также намеревается переместить из него (T&& &&) , компилятор рассматривает его как перемещаемый объект для этого конкретного использования.
Другие вопросы по тегам