c++templatesdecltypereference-wrapperdeclval

std::reference_wrapper, constructor implementation explaination


I have been trying to understand the implementation of std::reference_wrapper, from here, which is as follows:

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;
};

Although the implementation of std::reference_wrapper has been discussed hereand here,but none of it discusses the constructor implementation which i am confused about. My confusions are : 1.) Constructor is a template function , taking a type param (U) different from the template class param T. I have seen member functions of a class being template functions and depending on different type params then the type param of the template class, but i can't think how it is works here. There is a related question here, but i am not able to relate it with my confusion. 2.)I see the second type parameter in the constructor is further used to sfinae out something, but i did not understand howdetail::FUN<T>(std::declval<U>()) is evaluated.

Can someone please explain ?

Edit: This is an example added from microsoft.docs. A snippet of the example is:

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

With the implementation of reference_wrapper , and from A.1, how is the constructor of the reference_wrapper called ? Assuming that detail::FUN<T>(std::declval<U>() will be called with detail::FUN<T>(std::declval<int>(), should be a substitution failure because of the deleted overload(Assuming std::declval<int> will be read as an rvalue reference to int). What am i missing here ?


Solution

  • It's a technique you can use when you want the behaviour of the "forwarding reference", U&& in this case, but at the same time restrict what can bind to it.

    Deduction of T is aided by the deduction guide provided below. The detail::FUN<T>(std::declval<U>()) is there to ensure that the constructor is disabled when U is deduced to be an rvalue reference, by selecting the deleted overload and producing an invalid expression. It is also disabled if the U is another reference wrapper, in which case the copy constructor should be selected.

    Here are a few examples of valid and invalid reference_wrappers:

    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);
    

    As you can see, the call to the deleted FUN<T>(T&&) only comes into play in the last 4 cases: when you explicitly specify T, but attempt to construct from an rvalue.