8

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 ?

2
  • 4
    It is a very tricky way of making sure that you cannot create a std::reference_wrapper from a rvalue (e.g. a temporary) without affecting overload resolution in an adverse way. This specification of the constructor is a result of a defect report. Commented Jan 3, 2022 at 0:43
  • 1
    "will be called with detail::FUN<T>(std::declval<int>())": No, the constructor uses a forwarding reference, so if you pass the constructor a lvalue (which i is), then it will call detail::FUN<T>(std::declval<int&>()). declval returns a lvalue reference in this case and the non-deleted overload is chosen. Commented Jan 3, 2022 at 12:49

1 Answer 1

6

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks. I have added an example in the question to illustrate a use case when an int object is wrapped using reference_wrapper. I understand when you say the overload of detail::fun(). taking an rvalue reference param will be ignored, but would not that mean a case as simple as detail::FUN<T>(std::declval<int>() will be ignored ?
@warrior_monk In that case, the reference_wrapper's type parameter T is explicitly given as int, and U is deduced as int&, which results in the expression detail::FUN<int>(std::declval<int&>()) being valid. I see I made a mistake in my original answer: the reference_wrapper's own parameter T, not its constructor's, is the one being deduced using the deduction guide. I'll fix that and add some examples of valid and invalid uses.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.