The example on the page of std::ref
/std::cref
shows the use of std::ref
/std::cref
to pass arguments to std::bind
in a way that looks like std::bind
is taking arguments by reference, when in reality it takes them all by value.
Looking at that example only, I could also be ignorant about the existence of std::reference_wrapper
, and std::ref
would just be a function that allows the behavior exhibited by the linked example.
That's what I mean by std::ref
works in the title of the question and also in the following.
Mostly for fun I've tried implementing std::ref
myself, and I came up with this:
template<typename T>
struct ref_wrapper {
ref_wrapper(T& t) : ref(t) {}
T& ref;
operator T&() const {
return ref;
}
};
template<typename T>
ref_wrapper<T> ref(T& t) {
return ref_wrapper{t}; // Ooops
}
template<typename T>
ref_wrapper<const T> cref(const T& t) {
return ref_wrapper{t}; // Ooops
}
where on the lines marked as // Ooops
I have mistakely made use of CTAD because I was compiling with -std=c++17
. By changing ref_wrapper
to ref_wrapper<T>
and ref_wrapper<const T>
in the two cases corrects this.
Then I've had a peek into /usr/include/c++/10.2.0/bits/refwrap.h
.
On the one hand, I see that my implementation of ref
/cref
closely resembles that of std::ref
/std::cref
.
On the other hand, I see that std::reference_wrapper
is around 60 lines long! There's a lot of stuff in there, including noexcept
, macros, copy ctor, copy operator=
, get
.
I think most of that is not relevant to the use of std::reference_wrapper
only as a slave to std::ref
, but there's something which could be relevant, such as constructor taking a universal reference.
So my question is: what are the parts of std::reference_wrapper
necessary and sufficients for std::ref
to work, with respect to my skinny attempt?
I've just realized that there's a possible implementation of std::reference_wrapper
on cppreference (which is less noisy than the one from GCC). Even here, however, there are things I don't undertand the reason of, such as operator()
.
The logic that you're talking about is implemented entirely within std::bind
itself. The main functionality it needs from std::reference_wrapper
is the fact that it can be "unwrapped" (i.e., you can call .get()
on it in order to retrieve the underlying reference). When the call wrapper (i.e. object returned from std::bind
) is called, it simply checks whether any of its bound arguments is a std::reference_wrapper
. If so, it calls .get()
to unwrap it, then passes the result to the bound callable.
std::bind
is complicated because it is required to support various special cases, such as recursive bind
ing (this feature is now considered a design mistake), so instead of trying to show how to implement the full std::bind
, I'll show a custom bind
template that's sufficient for the example on cppreference:
template <class Callable, class... Args>
auto bind(Callable&& callable, Args&&... args) {
return [c=std::forward<Callable>(callable), ...a=std::forward<Args>(args)] () mutable {
c(detail::unwrap_reference_wrapper(a)...);
};
}
The idea is that bind
saves its own copy of the callable and each of the args. If an argument is a reference_wrapper
, the reference_wrapper
itself will be copied, not the referent. But when the call wrapper is actually invoked, it unwraps any saved reference wrapper argument. The code to do this is simple:
namespace detail {
template <class T>
T& unwrap_reference_wrapper(T& r) { return r; }
template <class T>
T& unwrap_reference_wrapper(reference_wrapper<T>& r) { return r.get(); }
}
That is, arguments that are not reference_wrapper
s are simply passed through, while reference_wrapper
s go through the second, more specialized overload.
The reference_wrapper
itself merely needs to have a relevant constructor and get()
method:
template <class T>
class reference_wrapper {
public:
reference_wrapper(T& r) : p_(std::addressof(r)) {}
T& get() const { return *p_; }
private:
T* p_;
};
The ref
and cref
functions are easy to implement. They just call the constructor, having deduced the type:
template <class T>
auto ref(T& r) { return reference_wrapper<T>(r); }
template <class T>
auto cref(T& r) { return reference_wrapper<const T>(r); }
You can see the full example on Coliru.
(The actual constructor of std::reference_wrapper
, as shown on cppreference, is complicated because it needs to satisfy the requirement that the constructor will be SFINAE-disabled if the argument would match an rvalue reference better than an lvalue reference. For the purposes of your question, it doesn't seem necessary to elaborate on this detail further.)