std::reference_wrapper,构造函数实现解释

8

我一直在尝试理解std::reference_wrapper的实现,从这里开始,其实现如下:

namespace detail {
template <class T> constexpr T& FUN(T& t) noexcept { return t; }
template <class T> void FUN(T&&) = delete;
}
 
template <class T>
class reference_wrapper {
public:
  // types
  typedef T type;
 
  // construct/copy/destroy
  template <class U, class = decltype(
    detail::FUN<T>(std::declval<U>()),
    std::enable_if_t<!std::is_same_v<reference_wrapper, std::remove_cvref_t<U>>>()
  )>
  constexpr reference_wrapper(U&& u) noexcept(noexcept(detail::FUN<T>(std::forward<U>(u))))
    : _ptr(std::addressof(detail::FUN<T>(std::forward<U>(u)))) {}
  reference_wrapper(const reference_wrapper&) noexcept = default;
 
  // assignment
  reference_wrapper& operator=(const reference_wrapper& x) noexcept = default;
 
  // access
  constexpr operator T& () const noexcept { return *_ptr; }
  constexpr T& get() const noexcept { return *_ptr; }
 
  template< class... ArgTypes >
  constexpr std::invoke_result_t<T&, ArgTypes...>
    operator() ( ArgTypes&&... args ) const {
    return std::invoke(get(), std::forward<ArgTypes>(args)...);
  }
 
private:
  T* _ptr;
};

尽管std::reference_wrapper的实现已经在这里这里讨论过,但都没有讨论我困惑的构造函数实现。我的困惑是: 1.)构造函数是一个模板函数,接受与模板类参数T不同的类型参数U。我见过类的成员函数是模板函数,并依赖于不同于模板类的类型参数,但我无法想象它在这里是如何工作的。有一个相关的问题here,但我无法将其与我的困惑联系起来。 2.)我看到构造函数中的第二个类型参数进一步用于sfinae出某些内容,但我不明白如何评估detail::FUN<T>(std::declval<U>())
有人能解释一下吗?

编辑:这是从microsoft.docs添加的示例。示例的一部分如下:

    int i = 1;
    std::reference_wrapper<int> rwi(i);  // A.1
    rwi.get() = -1;
    std::cout << "i = " << i << std::endl; //Prints -1

使用reference_wrapper实现后,从A.1开始,reference_wrapper的构造函数如何调用?假设会调用detail::FUN<T>(std::declval<int>(),因为重载中有一个deleted版本(假设std::declval<int>将被读取为对int的右值引用),所以应该是一次替换失败。我在这里错过了什么?

4
这是一种非常巧妙的方式,可以确保您无法从一个右值(例如临时对象)创建std::reference_wrapper,而不会对重载决议产生负面影响。构造函数的这个规定是缺陷报告的结果。 - user17732522
1
不,构造函数使用转发引用,因此如果您将构造函数传递给左值(即i),则它将调用detail::FUN<T>(std::declval<int&>())。在这种情况下,declval返回一个左值引用,并选择非删除重载。 - user17732522
1个回答

6

这是一项技术,可以在使用“转发引用”时(在本例中为U&&),同时限制其所需绑定的内容。

由下面提供的推导指南协助推导T。当推导出U为右值引用时,detail::FUN<T>(std::declval<U>())可确保禁用构造函数,通过选择删除的重载并生成无效表达式来实现。如果U是另一个引用包装器,则也会禁用它,此时应选择复制构造函数。

以下是一些有效和无效的reference_wrapper示例:

int i = 1; 

// OK: FUN<int>(std::declval<int&>()) is valid
std::reference_wrapper<int> rwi(i); 

// error: forming pointer to reference
std::reference_wrapper<int&> rwi2(i); 

// OK, uses deduction guide to find T = int
std::reference_wrapper rwi3(i);
std::reference_wrapper rwi4(++i);

// error: cannot deduce T, since there is no deduction guide for
// rvalue reference to T
std::reference_wrapper rwi5(std::move(i));

// error: substitution failure of FUN<int>(int&&)
std::reference_wrapper<int> rwi6(std::move(i));
std::reference_wrapper<int> rwi7(i++);
std::reference_wrapper<int> rwi8(i + i);
std::reference_wrapper<int> rwi9(2);

正如你所看到的,在最后四种情况中才会调用删除的 FUN<T>(T&&) :当你明确指定了 T,但尝试从右值构造时。

谢谢。我已经在问题中添加了一个示例,以说明使用reference_wrapper包装int对象的用例。当您说detail :: fun()的重载采用rvalue引用参数将被忽略时,我理解,但这是否意味着像detail :: FUN <T>(std :: declval <int>()这样简单的情况也会被忽略? - warrior_monk
在这种情况下,reference_wrapper的类型参数T被明确给定为int,而U被推断为int&,这导致表达式detail :: FUN <int>(std :: declval <int&>())有效。我发现我在原始答案中犯了一个错误:推断引导使用的不是reference_wrapper的构造函数参数,而是其自身的参数T。 我会修复它并添加一些有效和无效使用示例。 - sigma

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接