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

问题描述

The example on the page of std::ref/std::cref 展示了使用 std::ref/std::cref 将参数传递给 std::bind 的方式看起来像 std::bind 通过引用获取参数,当实际上,它需要所有的价值。

仅看那个示例,我也可能对 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、宏、复制构造函数、复制operator=get

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

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

我刚刚意识到 std::reference_wrapper on cppreference 的可能实现(比 GCC 中的噪声小)。然而,即使在这里,也有一些我不明白的原因,例如 operator()

解决方法

您所说的逻辑完全在 std::bind 内部实现。它需要从 std::reference_wrapper 获得的主要功能是它可以“解包”(,您可以对其调用 .get() 以检索基础引用)。当调用包装器(i.e.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 保存它自己的可调用和每个参数的副本。如果参数是 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

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