Логично ли это поведение std:: ref?

Рассмотрим этот код:

#include <iostream>
#include <functional>

int xx = 7;

template<class T>
void f1(T arg)
{
    arg += xx;
}

template<class T>
void f2(T arg)
{
    arg = xx;
}

int main()
{
    int j;

    j=100;
    f1(std::ref(j));
    std::cout << j << std::endl;

    j=100;
    f2(std::ref(j));
    std::cout << j << std::endl;
}

При выполнении этот код выводит

107
100

Я бы ожидал, что второе значение будет равным 7, а не 100.

Что мне не хватает?

Ответ 1

Небольшая модификация f2 дает ключ:

template<class T>
void f2(T arg)
{
    arg.get() = xx;
}

Теперь это делает то, что вы ожидаете.

Это произошло потому, что std::ref возвращает объект std::reference_wrapper<>. Оператор присваивания которого перегружает оболочку. (см. http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper/operator%3D)

Он не присваивает завернутую ссылку.

В случае f1 все работает так, как вы ожидали, потому что std::reference_wrapper<T> предоставляет оператор преобразования для T&, который будет связываться с неявной правой частью int неявный operator+.

Ответ 2

reference_wrapper имеет operator = и не явный конструктор, см. документацию.

Итак, даже если это удивительно, это нормальное поведение:

f2 перенаправляет локальный reference_wrapper на xx.

Ответ 3

arg = xx;

Локальный arg теперь относится к (читается как связывание с) xx. (И больше не относится к j)

arg += xx;

Неявный operator T& () применяется для соответствия аргументу operator += и, следовательно, добавление выполняется по упомянутому объекту i.e. j.

Таким образом, наблюдаемое поведение является правильным.