Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perfect return for functions modifying their argument

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.

like image 827
Hugues Avatar asked Mar 22 '23 08:03

Hugues


1 Answers

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.

like image 100
BЈовић Avatar answered Apr 06 '23 09:04

BЈовић