std::reference_wrapper 实现的唯一目的是 std::ref 工作的关键是什么?

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 显示了使用 std::ref/std::cref 将参数传递给 std::bind 的方式看起来像 std::bind 通过引用获取参数,而实际上它是按值取 all 的。

只看那个例子,我也可能不知道 std::reference_wrapper 的存在,而 std::ref 只是一个允许链接示例表现出的行为的函数。

这就是我所说的 std::ref 在问题标题和以下内容中有效 的意思。

主要是为了好玩,我尝试自己实现 std::ref,然后我想到了这个:

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
}

在标记为 // Ooops 的行中,我错误地使用了 CTAD,因为我正在使用 -std=c++17 进行编译。通过在这两种情况下将 ref_wrapper 更改为 ref_wrapper<T>ref_wrapper<const T> 可以更正此问题。

然后我看了一眼/usr/include/c++/10.2.0/bits/refwrap.h

一方面,我发现我对 ref/cref 的实现与 std::ref/std::cref 的实现非常相似。

另一方面,我看到 std::reference_wrapper 大约有 60 行长!里面有很多东西,包括noexcept、宏、copy ctor、copy operator=get.

我认为其中大部分与 std::reference_wrapper 的使用无关,只是作为 std::ref[=71 的奴隶=],但有些东西可能是相关的,例如构造函数采用通用引用。

所以我的问题是:std::reference_wrapper 的哪些部分是 std::ref 工作的必要和充分条件,就我的瘦身尝试而言?

我刚刚意识到 std::reference_wrapper on cppreference 有一个可能的实现(比 GCC 的那个更不嘈杂)。然而,即使在这里,也有一些我不理解的原因,例如 operator().

您所说的逻辑完全在 std::bind 内部实现。它需要 std::reference_wrapper 的主要功能是它可以“展开”(,您可以对其调用 .get() 以检索底层参考)。当调用包装器(即从 std::bind 返回的 对象)被调用时,它只是检查它的任何绑定参数是否是 std::reference_wrapper。如果是这样,它调用 .get() 来解包它,然后将结果传递给绑定的可调用对象。

std::bind 很复杂,因为它需要支持各种特殊情况,例如递归 binding(这个特性现在被认为是设计错误),所以与其试图展示如何实施完整的 std::bind,我将展示一个自定义 bind 模板,该模板足以满足 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)...);
    };
}

想法是 bind 保存自己的 copy 的可调用对象和每个参数。如果参数是 reference_wrapper,将复制 reference_wrapper 本身,而不是所指对象。但是当实际调用调用包装器时,它会解开所有保存的引用包装器参数。执行此操作的代码很简单:

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(); }
}

也就是说,不是 reference_wrapper 的参数会被简单地传递,而 reference_wrapper 会经过第二个更专门的重载。

reference_wrapper本身只需要有相关的构造器和get()方法即可:

template <class T>
class reference_wrapper {
  public:
    reference_wrapper(T& r) : p_(std::addressof(r)) {}
    T& get() const { return *p_; }

  private:
    T* p_;
};

refcref的功能很容易实现。他们只是调用构造函数,推导出类型:

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); }

你可以看到完整的例子on Coliru

(如 cppreference 所示,std::reference_wrapper 的实际构造函数很复杂,因为它需要满足以下要求:如果参数匹配右值引用比左值更好,则构造函数将禁用 SFINAE参考。就你的问题而言,似乎没有必要进一步详细说明这个细节。)