How to implement a make_rv function in c++

90 Views Asked by At

I want to write a template which can turn lvalue/rvalue to rvalue using universal reference like std::forward For rvalue, just forward it. For lvalue, copy it.

it is used as below

template<typename T>
void func(T&& arg) {
    f_take_rv(make_rv<T>(arg)); // f_take_rv takes right value only
}

Here is my implement

template<typename T> // for lvalue
constexpr T make_rv(typename std::remove_reference<T>::type& arg) {
    return arg;
}
template<typename T> // for rvalue
constexpr T&& make_rv(typename std::remove_reference<T>::type&& arg) {
    return static_cast<T&&>(arg);
}

However, it always goes to the lvalue one

I also tried std::enable_if to control its type deduction.

template<typename T>
constexpr typename std::enable_if<
    std::is_reference<T>::value,
    typename std::remove_reference<T>::type
>::type make_rv(typename std::remove_reference<T>::type& arg) { return arg; }

template<typename T>
constexpr typename std::enable_if<
    !std::is_reference<T>::value,
    T
>::type&& make_rv(typename std::remove_reference<T>::type&& arg) {
    return static_cast<T&&>(arg);
}

But it failed for literal string, like "str": no matching function for call to 'make_rv<const char(&)[4]>(const char [4])'

Could you tell me how to implement it

gcc implement of std::forward for reference:

template<typename _Tp>  // forwarding an lvalue
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept {
    return static_cast<_Tp&&>(__t);
}
template<typename _Tp>  // forwarding an rvalue
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept {
    return static_cast<_Tp&&>(__t);
}
3

There are 3 best solutions below

4
hczstev On

I found a version works for me

template<typename T> // for lvalue, copy it
constexpr typename std::enable_if<
    std::is_reference<T>::value,
    typename std::decay<T>::type
>::type make_rv(const T& arg) { return arg; }

template<typename T> // for rvalue, forward it
constexpr typename std::enable_if<
    !std::is_reference<T>::value,
    T
>::type&& make_rv(T& arg) {
    return static_cast<T&&>(arg);
}
6
alagner On

I would go probably the easiest route possible:


template<typename T>
constexpr auto copy_l_forward_r(T&& arg)
    -> typename std::conditional<std::is_lvalue_reference<T>::value && !std::is_array<T>::value,
        typename std::remove_cv<typename std::remove_reference<T>::type>::type, T&&>::type

{   
    return std::forward<T>(arg);
}

https://godbolt.org/z/WbbWPa5PE

The main question is what to do about references to C-style arrays. They can be returned "as-is" (thanks @JosephThomson for that comment), but they can also be decayed to a pointer.

To expand it a bit more: the whole confusion (as this is a recurring topic in one form or another) stems from the way type deduction works in C++.

Consider a function taking a forwarding/universal reference: template<typename T> void f(T&& f) {/*whatever*/} Imagine it's called with an integer literal: f(3); What are types T and arg are going to be? Hint: they're not going to be the same. T is an int, decltype(arg) is is int&&.

Ok, so what about int x = 4; f(x);? T is int&, decltype(arg) int&. Ok, so far so good.

Now the interesting part comes. Consider the following code snippet:

static int i = 3;
int& lvint() { return i; }
int&& rvint() {return std::move(i);}

What are the types inside f going to be? f(lvint()); Now, both T and decltype(arg) are int&. Still, nice, easy and predictable.

Now take: f(rvint()); and this is where the plot thickens. decltype(arg) is int&&, but T is int. Not a reference.

I cannot point my finger at the exact chapter in the standard that is responsible for this, the closest one I can think of is this:

A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction. https://eel.is/c++draft/temp.deduct.call

My personal rule of thumb (but just a personal opinion, so feel free to disagree) is this: it is more predictable to operate on the decltype(arg) rather than T when fiddling with perfect forwarding. Or, when operating on T: keep in mind that T can be a non-reference type unless it's a lvalue one.

And some examples to illustrate all the above: https://godbolt.org/z/dzzqdrM8T

1
Joseph Thomson On

If I understand you correctly, nothing fancy is required:

template <typename T>
typename std::remove_const<T>::type make_rv(T& t)
{
    return t;
}

template <typename T>
T&& make_rv(T&& t)
{
    return std::forward<T>(t);
}