4

There is extensive discussion of perfect forwarding in templated functions, to allow efficient passing of either lvalues or rvalues arguments as parameters to other functions.

However, I am not able to find discussion of perfect return or equivalently perfect pass-through. (The related question Perfect pass-through does not fully address this.)

Consider the case of a function that modifies a range and should return the modified range. We would need two separate functions to efficiently address the cases of lvalue and rvalue arguments:

// Given a reference to an lvalue range, return a reference to the same modified range.
// (There is no allocation or move.)
template<typename T> T& sortr(T& r) {
    std::sort(std::begin(r),std::end(r));
    return r;
}

// Given an rvalue range, return the same range via (hopefully) a move construction.
template<typename T> T sortr(T&& r) {
    std::sort(std::begin(r),std::end(r));
    return std::move(r);
}

Of course, including both definitions leads to ambiguity errors because the second one also matches lvalue references.

A motivating example (and test use) is the following:

#include <iostream>
#include <vector>
#include <algorithm>

std::ostream& operator<<(std::ostream& os, const std::vector<int>& v) {
    os << "["; for (int i : v) { os << " " << i; } os << " ]\n"; return os;
}

int main() {
    std::vector<int> c1{3,4,2,1};
    std::cerr << sortr(c1) << sortr(std::vector<int>({7,6,5,8}));
}

Can we define both versions of sortr using a single template<typename T> definition?

The challenge is that declaring the return type as T&& will only result (after template matching) in a T&& or T& return type, not T.

Is it possible to define the appropriate template function using meta-programming on the return type? Something like the following:

template<typename T> auto sortr(T&& r) ->
    typename std::conditional<std::is_lvalue_reference<T>::value, T&, T>::type
{
    std::sort(std::begin(r),std::end(r));
    return std::forward<T>(r);
}

This seems to work, but I am unsure if it is safe and wise. Any guidance would be welcome.

Community
  • 1
  • 1
Hugues
  • 2,865
  • 1
  • 27
  • 39
  • 2
    If `r` is bound to an rvalue of type `U`, `T` will be `U`. If `r` is bound to an lvalue of type `U`, `T` will be `U&`. As such, just use `T` as the return type, together with `std::forward`. – Xeo Sep 19 '13 at 18:12
  • Does this talk help? http://channel9.msdn.com/Events/GoingNative/2013/An-Effective-Cpp11-14-Sampler – Adam Burry Sep 19 '13 at 18:27
  • "Of course, including both definitions leads to ambiguity errors because the second one also matches lvalue references"... if your compiler sucks. There is a clause in the standard that says that if you have a `T&&` and a `T&` and both match, then `T&` wins. – Yakk - Adam Nevraumont Sep 19 '13 at 19:38

1 Answers1

7

You need to use universal reference. In that case, you can return just T, but you need to change your template function to this :

template<typename T> T sortr(T && r) {
    std::sort(std::begin(r),std::end(r));
    return std::forward<T>(r);
}

Full example :

#include <iostream>
#include <utility>
#include <type_traits>
#include <vector>
#include <algorithm>

template<typename T> T sortr(T && r) {
    std::sort(std::begin(r),std::end(r));
    return std::forward<T>(r);
}

std::vector<int> foo()
{
  return std::vector<int>(5,2);
}

void test( std::vector<int> & )
{
  std::cout<<"lvalue" << std::endl;
}
void test( std::vector<int> && )
{
  std::cout<<"rvalue" << std::endl;
}

int main()
{
  std::vector<int> v(1,6);

  test( sortr( foo() ) );
  test( sortr( v ) );
}

If you do not know what universal reference is, take a look into this talk by Scott Meyers.

BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • Your `std::begin` and `std::end` is best done via a `using std::begin; using std::end;` then invoking `begin` and `end` naked. This enables ADL on the type `T`, allowing custom classes with non-member `begin` `end` to work, just like `for( auto x:Y )` loops. – Yakk - Adam Nevraumont Sep 19 '13 at 19:41
  • 1
    As a comment, `template T&& sortr(T && r) { std::sort(std::begin(r),std::end(r)); return std::forward(r); }` is tempting, but it has a subtle issue: if you assign the result of `sortr` to a reference, you don't get proper lifetime extension. This causes problems when you `for( auto x: sortr( foo() ) )`, as (by the standard) it stores the iterated object in a reference. – Yakk - Adam Nevraumont Sep 19 '13 at 19:43
  • @Yakk Regarding begin/end, I just copy pasted the code. I would probably call member methods. – BЈовић Sep 19 '13 at 20:02