Search code examples
c++templatesvariadic-templatesmovetemplate-argument-deduction

How does std::bind take variadic arguments by value, even with its universal reference?


The function signature for std::bind() is as follows:

template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );

So args is a variadic template universal reference, if I understand correctly. The traditional template type deduction rules for universal references are as follows:

  1. If the argument passed to T is an lvalue, then T is deduced to an lvalue reference
  2. If the argument passed to T is an rvalue, then T is deduced to an lvalue

... and I think these rules would apply to each arg in args individually, meaning that all lvalues passed into std::bind() as an argument to the functor would be passed by reference. However this contradicts the program below:

#include <iostream>
#include <functional>

void function(int& n) {
  n++;
}

int main() {
    int n = 0;
    auto functor = std::bind(function, n);
    functor();
    std::cout << n << std::endl; // 0, not 1.
    return 0;
}

In order to get n to be passed by reference, you must do so explicitly via std::ref(n), which really confuses me given what (little) I know about universal references and perfect forwarding. How does std::bind() take anything by value when it uses universal references, which would otherwise consume lvalues as references?


Solution

  • It has almost nothing to do with the signature, it is a design choice. std::bind must of course store all its bound arguments somehow and it stores them as values. The "universality" is only used to properly construct them - by move or copy.

    std::ref is also stored by value but due to its nature, the wrapped object is "stored" by reference.

    std::thread has exactly the same behaviour. One can argue that (move) constructing a copy by default is safer because both returned objects tend to outlive locals which are the likeliest to be captured.