Return element of pair with perfect forwarding

156 Views Asked by At

I want to write a function that will evaluate some expression that returns a pair (possibly as a reference, and possibly containing references) and returns the second element, but which forwards references rather than copying them.

Here's an example that doesn't quite work:

#include <utility>
#include <type_traits>
#include <iostream>

using namespace std;

template<typename F>
decltype(auto) foo(F&& func)
{
    return get<1>(std::forward<F>(func)());
}

pair<int, int> value_of_values()
{
    return make_pair(2, 3);
}

pair<int &, int &> value_of_refs()
{
    static int x = 4;
    static int y = 5;
    return pair<int &, int &>(x, y);
}

pair<int, int> &ref_of_values()
{
    static pair<int, int> p(6, 7);
    return p;
}

int main()
{
    cout << foo(value_of_values) << '\n';
    cout << foo(value_of_refs) << '\n';
    cout << foo(ref_of_values) << '\n';
    return 0;
}

The problem is with foo(value_of_values): value_of_values returns a prvalue, but get<1> returns an rvalue reference, so foo returns an rvalue reference to a temporary that promptly disappears.

I know I can probably do some template metaprogramming with decltype to distinguish the case where the function return value is a prvalue containing a non-reference (which must be returned as a non-reference) from the other cases, but is there a more elegant solution?

(using std::forward<F>(func)().second instead of get<1>(...) doesn't help: if it's parenthesised one gets exactly the same problem because member access on a prvalue is an xvalue, causing decltype(auto) to infer int &&, and if it's not parenthesized then the ref_of_values case returns a copy instead of a reference because of the semantics of decltype for unparenthesized member access expressions).

1

There are 1 best solutions below

5
On

I know I can probably do some template metaprogramming with decltype to distinguish the case where the function return value is a prvalue containing a non-reference (which must be returned as a non-reference) from the other cases, but is there a more elegant solution?

You can first use universal reference to receive the return value of func, and then decide whether to move construct the object according to whether it is an rvalue.

template<typename F>
decltype(auto) foo(F&& func)
{
  auto&& x = std::get<1>(std::forward<F>(func)());
  using T = decltype(x);
  if constexpr (std::is_lvalue_reference_v<T>)
    return x;
  else
    return std::remove_reference_t<T>(std::move(x));
}