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 ?
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_wrapper
s:
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.