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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With