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
很复杂,因为它需要支持各种特殊情况,例如递归 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
保存自己的 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_;
};
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。
(如 cppreference 所示,std::reference_wrapper
的实际构造函数很复杂,因为它需要满足以下要求:如果参数匹配右值引用比左值更好,则构造函数将禁用 SFINAE参考。就你的问题而言,似乎没有必要进一步详细说明这个细节。)
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
很复杂,因为它需要支持各种特殊情况,例如递归 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
保存自己的 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_;
};
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。
(如 cppreference 所示,std::reference_wrapper
的实际构造函数很复杂,因为它需要满足以下要求:如果参数匹配右值引用比左值更好,则构造函数将禁用 SFINAE参考。就你的问题而言,似乎没有必要进一步详细说明这个细节。)