Прежде чем, тронуть std::reference_wrapper давайте рассмотрим несколько задач, некоторые из которых могут показаться странными на первый взгляд. 

Задача первая

Ссылки в C++ не могут выступать в качестве типа элементов массива или контейнера STL:

std::vector< std::string& > cont; // Ошибка на этапе компиляции

Конечно можно использовать указатели в качестве элементов контейнера, однако в этом случае оказывается задействован синтаксис указателей. Поэтому, иногда очень хочется организовать контейнер, хранящий ссылки. Как этого можно добиться? Читаем далее.

Задача вторая

Инициализация ссылки происходит при её объявлении. После объявления ссылки её невозможно привязать к другой переменной. Однако можно легко создать похожее по семантике поведение и сделать что-то вроде "повторной инициализации" ссылки.

Задача третья

Многие алгоритмы в STL могут принимать функторы (функциональные объекты) в качестве своих аргументов. Передача аргументов алгоритму, в том числе и функторов, происходит по значению. Это затрудняет использование внутреннего состояния функторов. К примеру: как подсчитать количество операций сравнения в алгоритме std::sort?

Решим все эти задачи.

Начнем давать ответы в обратном порядке.

Задача третья - ответ

Третья задача решается с помощью шаблонных функций std::ref и std::cref объявленных в заголовке <functional>. Первая возвращает ссылку на объект, вторая возвращает константную ссылку. Эти функции очень часто нужны при работе, например, с std::bind. Причина одна - передать в связыватель ссылку, а не копию объекта. Если Вы работаете с потоками (std::thread) и Вам нужно передать аргументы по ссылке при инициализации объекта потока, Вам также понадобятся функции std::ref и std::cref

Для того, чтобы подсчитать количество операций сравнения в алгоритме std::sort, можно также воспользоваться функциями std::ref или std::cref. В этом случае, функтор будет передан по ссылке, и мы сможем использовать его внутреннее состояние, как внутри алгоритма, так и после его завершения. Пример ниже:

Аналогичные примеры можно привести и для других алгоритмов STL. То что, это актуальная задача подтверждает, тот факт, что алгоритм std::for_each возвращает свой функтор. И мы можем работать с ним после выполнения алгоритма. Но так делает только std::for_each. Для остальных алгоритмов можно использовать описанный подход с std::ref и std::cref.


Пришло время открыть "магию" функций std::ref и std::cref, и узнать, как они обеспечивают передачу аргументов по ссылке. Лучше всего будет взглянуть на их реализацию, например, в GCC:

Оказывается, std::ref и std::cref почти ничего и не делают. Принимая свой аргумент по ссылке или константной ссылке, они создают экземпляр типа reference_wrapper и просто возвращают созданный объект. Именно этот объект далее и будет передан в качестве аргумента. Но как тип reference_wrapper "эмулирует" нужные нам ссылки? Лучше один раз увидеть, чем долго про это рассказывать, поэтому снова обратимся к GCC и посмотрим реализацию шаблона класса reference_wrapper. Чтобы не потерять сути, немного упростим данную реализацию, скрыв один интересный момент. Итак, reference_wrapper:

Это простой шаблон класса, который хранит внутри себя указатель на данные. Получив ссылку в конструкторе, reference_wrapper узнает адрес переменной __indata с помощью функции std::addressof<> (эта функция была очень хорошо описана на ArtLang здесь). У reference_wrapper определены копирующий конструктор и оператор присваения, а это значит что объекты этого типа могут легко инициализироваться повторно в отличии от ссылкок. Ну и самое главное, определен оператор приведения к ссылке на тип _Tp. Т.е. в нужный момент reference_wrapper ведет себя как необходимая нам ссылка. То что нужно!

Итак, когда мы используем для передачи аргумента функции std::ref или std::cref, мы передаем не копию объекта, не ссылку на него, а экземпляр reference_wrapper, который внутри себя хранит указатель на передаваемый объект.


Задача вторая - ответ

Инициализация ссылки происходит при её объявлении. После объявления ссылки её невозможно привязать к другой переменной. Однако можно легко создать похожее по семантике поведение, если использовать reference_wrapper. Покажем это на примере. 

Допустим есть список, который хранит 10000 строк std::string. Необходимо найти самую длинную строку (в которой больше всего символов). Список не отсортирован по этому критерию. Будем использовать метод прямого перебора и хранить самую длинную строку во временной переменной. При этом желательно избежать копирования строк во временную переменную. Конечно для этого можно, в качестве временной переменной, взять итератор списка, но мы возьмем объект reference_wrapper. Пример ниже:

Пример достаточно надуманный, однако, он наглядно демонстрирует, как легко повторно инициализировать reference_wrapper. Обратите внимание на то, что когда компилятор не может привести тип, приходится вызывать метод get(). Этот метод явно возвращает ссылку на "обернутый" объект. Если представить на секунду, что вызова get() можно было бы избежать, то мы бы даже не смогли отличить настоящие ссылки, от объектов  reference_wrapper 


Задача первая - ответ

Контейнерные классы STL реализуют семантику значений, а не ссылок. Контейнеры создают копии элементов, которые они содержат. Но, если копирование элементов требует избыточных затрат ресурсов, или нужно, чтобы несколько контейнеров размещали одни и теже элементы - приходится использовать "умные" указатели. Но есть и другой вариант. Использование reference_wrapper

Рассмотрим пример, в котором контейнер std::vector, хранит "ссылки" (объекты reference_wrapper). Пример взят с сайта cppreference.com:

Вывод программы:

Contents of the list: -4 -3 -2 -1 0 1 2 3 4 
Contents of the list, shuffled: 0 -1 3 4 -4 1 -2 -3 2 
Doubling the values in the initial list...
Shuffled vector actually contains references: 0 -2 6 8 -8 2 -4 -6 4

Используя такие "контейнеры ссылок", Вы всегда должны гарантировать, что элементы, на которые Вы ссылаетесь, существуют. Если в этом примере контейнер std::list вдруг внезапно будет уничтожен, пользоваться "контейнером ссылок" (std::vector) будет уже нельзя.

Преимущество этого кода заключается в том, что не используется синтаксис указателей. Даже вызова get() удалось избежать.

В заключение хотелось бы сказать: reference_wrapper возможно не очень популярная "штука" из STL, но Вы врядли сможете избежать её использования в своих программах. Хотя бы косвенно - через вызовы std::ref и std::cref - но Вы задействуете эту простую обертку вокруг обычных указателей.


P. S.: Буду рад любым комментариям.