Curious
Curious

Reputation: 21510

A little hazy about std::ref() and std::bind() with variadic templates

I have read many posts about variadic templates and std::bind but I think I am still not understanding how they work together. I think my concepts are a little hazy when it comes to using variadic templates, what std::bind is used for and how they all tie together.

In the following code my lambda uses the dot operator with objects of type TestClass but even when I pass in objects of type std::ref they still work. How is this exactly? How does the implicit conversion happen?

#include <iostream>
using std::cout;
using std::endl;
#include <functional>
#include <utility>
using std::forward;

class TestClass {
public:
    TestClass(const TestClass& other) {
        this->integer = other.integer;
        cout << "Copy constructed" << endl;
    }
    TestClass() : integer(0) {
        cout << "Default constructed" << endl;
    }
    TestClass(TestClass&& other) {
        cout << "Move constructed" << endl;
        this->integer = other.integer;
    }

    int integer;
};

template <typename FunctionType, typename ...Args>
void my_function(FunctionType function, Args&&... args) {
    cout << "in function" << endl;
    auto bound_function = std::bind(function, args...);
    bound_function();
}

int main() {

    auto my_lambda = [](const auto& one, const auto& two) {
        cout << one.integer << two.integer << endl;
    };

    TestClass test1;
    TestClass test2;
    my_function(my_lambda, std::ref(test1), std::ref(test2));

    return 0;
}

More specifically, I pass in two instances of a reference_wrapper with the two TestClass objects test1 and test2, but when I pass them to the lambda the . operator works magically. I would expect that you have use the ::get() function in the reference_wrapper to make this work but the call to the .integer data member works..

Upvotes: 9

Views: 625

Answers (2)

Niall
Niall

Reputation: 30605

It is important to note that with std::bind;

The arguments to bind are copied or moved, and are never passed by reference unless wrapped in std::ref or std::cref.

The "passed by reference" above is achieved because std::ref provides a result of std::reference_wrapper that is a value type that "wraps" the reference provided.

std::reference_wrapper is a class template that wraps a reference in a copyable, assignable object. It is frequently used as a mechanism to store references inside standard containers (like std::vector) which cannot normally hold references.

By way of an example of what bind's unwrapping of the reference does (without the bind);

#include <iostream>
#include <utility>
#include <functional>

int main()
{
    using namespace std;
    int a = 1;
    auto b = std::ref(a);
    int& c = b;
    cout << a << " " << b << " " << c << " " << endl; // prints 1 1 1
    c = 2;
    cout << a << " " << b << " " << c << " " << endl; // prints 2 2 2
}

Demo code.

Upvotes: 0

Anton Savin
Anton Savin

Reputation: 41301

The reference unwrapping is performed by the result of std::bind():

If the argument is of type std::reference_wrapper<T> (for example, std::ref or std::cref was used in the initial call to bind), then the reference T& stored in the bound argument is passed to the invocable object.

Corresponding standardese can be found in N4140 draft, [func.bind.bind]/10.

Upvotes: 5

Related Questions