【问题标题】:What is the crux of std::reference_wrapper implementation for the only purpose of makin std::ref work?为了使 std::ref 工作的唯一目的, std::reference_wrapper 实现的关键是什么?
【发布时间】:2021-04-14 13:52:14
【问题描述】:

The example on the page of std::ref/std::cref 显示使用std::ref/std::cref 将参数传递给std::bind,看起来std::bind 通过引用获取参数,而实际上它通过值获取所有参数。

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

这就是我在问题标题中以及以下内容中 std::refworks 的意思。

主要是为了好玩,我自己尝试实现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&lt;T&gt;ref_wrapper&lt;const T&gt; 可以更正此问题。

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

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

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

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

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

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

【问题讨论】:

  • 引用包装器的许多功能应该如何表现在 C++ 标准的其他部分。 std::threadstd::bind 之类的东西知道 std::referene_wrapper 及其工作原理,因此他们可以利用这些知识来处理它。

标签: c++ c++11 ref reference-wrapper


【解决方案1】:

您所说的逻辑完全在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_wrappers 的参数被简单地传递,而reference_wrappers 经历第二个更专业的重载。

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 的要求。对于出于您的问题的目的,似乎没有必要进一步详细说明这个细节。)

【讨论】:

  • 如果reference_wrapper 提供operator T&amp;() const 成员,那么拥有/使用/需要get 成员又有什么意义?不使用转换运算符而不是get 避免需要unwrap_reference_wrapper,从而在第一块代码中有c(a...);?这样做会导致从 reference_wrapper 覆盖到 callable/c 期望的类型,我错了吗?
  • @Enlico 很明显,您需要其中之一。这个玩具示例使用.get(),但也可以编写为使用operator T&amp;。我不确定为什么标准的两者都有 - 这确实是一个单独的问题。
  • 好吧,我的问题实际上是给你的,因为我不确定我在之前的评论中写的内容是否完全正确。此外,您使用 get 的解决方案比我想的更冗长,这一简单的事实让我觉得我想错了,所以我想听听您的意见。
  • @Enlico 我尝试遵循std::bind 的精神——这需要明确解包reference_wrapper。这在某些情况下可能很重要,例如,可调用对象本身就是模板,或者可调用对象需要某种类型,需要从 T&amp; 进行用户定义的转换。 (续...)
  • 另一个想法是,我最初的“认知”是 std::ref 是一种通过值参数策略“欺骗”std::bind 的方法,这与您声称它是 std::bind 的说法相冲突让那件事发挥作用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-08
  • 2020-06-07
  • 1970-01-01
  • 1970-01-01
  • 2020-02-09
相关资源
最近更新 更多