TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.36K subscribers
Post #121 1.48K
Универсальные ссылки

Вообще говоря, вся эта серия постов началась с просьбы нашего подписчика Сергея Нефедова объяснить зачем нужны универсальные ссылки. Дождались! 🤩

В предыдущей статье я сделал акцент:

Тип rvalue reference задаётся с помощью && перед именем класса.

ОДНО БОЛЬШОЕ НО! Вместо имени класса может быть установлен параметр-тип шаблона:
template<typename T>
void foo(T &&message)
{
...
}


Ожидается, что из него будет выведен тип rvalue reference, но это не всегда так. Такие ссылки позволяют с одной стороны определить поведения для работы с xvalue, а с другой, неожиданно, для lvalue.

В своё время Scott Meyers, придумал такой термин как универсальные ссылки, чтобы объяснить некоторые тонкости языка. Рассмотрим на примере вышеупомянутой foo:
std::string str = "blah blah blah";

// Передает lvalue
foo(str);

// Передает xvalue (rvalue reference)
foo(std::move(str));


Оба вызова функции foo будут корректны, если не брать во внимание реализацию foo. Живой пример

Универсальная ссылка (т.н. universal reference) — это переменная или параметр, которая имеет тип T&& для выведенного типа T. Из неё будет выведен тип rvalue reference, либо lvalue reference. Это так же касается auto переменных, т.к. их тип тоже выводится.

Расставляем точки над i вместе со Scott Meyers:
Widget &&var1 = someWidget;
// ~~^~~
// rvalue reference

auto &&var2 = var1;
// ~~^~~
// universal reference

template<typename T>
void f(std::vector<T> &&param);
// ~~^~~
// rvalue reference

template<typename T>
void f(T &&param);
// ~~^~~
// universal reference


В соответствии с этим маленьким нюансом поведение может меняться внутри функции foo. Банально, можно накодить тормозящее копирование вместо производительной передачи ресурса.

Я немного изменил предыдущий пример: https://compiler-explorer.com/z/EzddYhjdv. В зависимости от выведенного типа, строка будет либо скопирована, либо перемещена. Соответственно, в области видимости функции main объект либо выводит текст, либо нет (т.к. ресурс был передан другому объекту внутри foo).

Причем, это не работает, если T — параметр-тип шаблонного класса:
template<class T>
class mycontainer
{
public:
void push_back(T &&other) { ... }
~~~^~~~
rvalue reference
...
};


Пример: https://compiler-explorer.com/z/We4qzG5xG

Получается, что в универсальные ссылки заложен дуализм поведения. Зачем же так было сделано? А за тем, что существуют template parameter pack:
template<class... Ts>
void foo(Ts... args)
{
bar(args...);
}

foo(std::move(string), value);
~~~~^~~~ ~~^~~~
xvalue lvalue


Как мы видим, разные аргументы вызова foo могут относиться к разным категориям выражений.

Кстати, если не знать и не пытаться в эти тонкости, то можно вполне спокойно использовать стандартные структуры. Если говорить с натяжкой, то можно, конечно, сказать, что такая универсальность может снижать порог вхождения в C++. Не знаешь — пишешь просто рабочий код, а знаешь — пишешь ещё и эффективный.

Другое дело, непонятно, почему нельзя было для универсальных ссылок сделать отдельный синтаксис? Например, добавить T &&&. Т.к. сейчас это рушит всю концептуальную целостность системы типов. Если это планировалось как гибкий механизм, то он граничит с полной дезориентацией разработчиков 😊

Я думаю, что нам еще нужны посты на разбор этой темы, чтобы это в голове уложилось. А пока будем развивать тему в сторону move семантики. Не забываем об исключениях в перемещающем конструкторе, а так же про оптимизации RVO/NRVO.

#cppcore #memory #algorithm #hardcore
  • ❤ 14
  • 👍 10
  • 🔥 7
  • ⚡ 2
More from @grokaemcpp
  1. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  2. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  3. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  4. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  5. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
  6. Sep 25, 2026​​std::spanstream #опытным Радостная весть для всех, кто пользуется iostreams! В C++23 доб…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →