简体   繁体   中英

What is the crux of std::reference_wrapper implementation for the only purpose of makin std::ref work?

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 the 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" ( ie , you can call .get() on it in order to retrieve the underlying reference). When the call wrapper ( ie 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.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM