问题描述
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
很复杂,因为它需要支持各种特殊情况,例如递归 bind
ing(此功能现在被认为是设计错误),因此与其尝试展示如何实现完整的 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_;
};
ref
和 cref
函数很容易实现。他们只是调用构造函数,推导出类型:
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。就您的问题而言,似乎没有必要进一步详细说明这一细节。)