2

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().

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • 1
    A lot of of how the functionality of reference wrapper should behave is in other parts of the C++ standard. Things like `std::thread` and `std::bind` know about `std::referene_wrapper` and how it works so they can use that knowledge to work with it. – NathanOliver Jan 08 '21 at 20:50

1 Answers1

2

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 binding (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_wrappers are simply passed through, while reference_wrappers 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.)

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • If `reference_wrapper` offers a `operator T&() const` member, what is the point of having/using/needing also a `get` member? Wouldn't using the conversion operator instead of `get` avoid the need for a `unwrap_reference_wrapper`, thus having `c(a...);` in the first chunk of code? Doing so would cause the coversion from `reference_wrapper` to the type that the `callable`/`c` expects, am I wrong? – Enlico Jan 09 '21 at 17:41
  • @Enlico Well, it's clear that you need one or the other. This toy example uses `.get()`, but it could just as well have been written to use `operator T&`. I'm not sure why the standard one has both - that's really a matter for a separate question. – Brian Bi Jan 09 '21 at 17:42
  • 1
    Well, mine is actually a question to you, as I'm not sure that what I write in my previous comment is entirely correct. Furthermore, the simple fact that your solution using `get` is more verbose than the one I'm supposing, makes me think that I'm supposing something wrong, and so I'd like your opinion about it. – Enlico Jan 09 '21 at 17:44
  • 1
    @Enlico I try to follow the spirit of `std::bind` - which requires the `reference_wrapper` to be explicitly unwrapped. This can matter in some cases, e.g., the callable is itself a template, or the callable expects some type that requires a user-defined conversion from `T&`. (continued ...) – Brian Bi Jan 09 '21 at 17:47
  • Another thought is that my initial "perception" is that `std::ref` is a way to "trick" `std::bind`'s by value arugment policy, which clashes with your claim that it's `std::bind` to make that thing work. – Enlico Jan 09 '21 at 17:47
  • @Enlico (... continued) if you want to rewrite this example using `operator T&`, I believe you can do so. – Brian Bi Jan 09 '21 at 17:48
  • Oh, sure, if the callable is a template, it would instantiate on `reference_wrapper` without triggering the conversion _from_ `reference_wrapper`. Ok, I'm convinced! – Enlico Jan 09 '21 at 17:48
  • @Enlico `bind` could have been specified to work in one of two ways: (1) store all arguments by value, then special-case on `reference_wrapper` when calling, or (2) unwrap the `reference_wrapper` upon initialization (storing a reference instead of a copy) so no special case is needed when calling. I am not sure why the standard one does (1) rather than (2); maybe it's just because reference members are annoying. – Brian Bi Jan 09 '21 at 17:50