2

Suppose I want to have a function foo taking either lvalue or rvalue reference as the parameter.

I could break it into two overloads taking lvalue and rvalue references.

void foo(int& a){/*some impl*/} // lvalue ref
void foo(int&& a){foo(a);} // rvalue ref
int main(){
  int a;
  foo(a); // lvalue
  foo(1); // rvalue
}

It does work but is pretty verbose. I could improve it using templates.

template<typename T>
void foo(T &&a) { /*some impl*/ }
int main(){
  int a;
  foo(a); // lvalue, T = int&
  foo(1); // rvalue, T = int
}

For a binary function the template would need to take two template parameters.

template<typename T1, typename T2>
void foo(T1 &&a, T2 &&b) { /*some impl*/ }
int main(){
  int a;
  foo(a, a); // (lvalue, lvalue), T1 = int&, T2 = int&
  foo(1, 1); // (rvalue, rvalue), T1 = int,  T2 = int
  foo(1, a); // (rvalue, lvalue), T1 = int,  T2 = int&
  foo(a, 1); // (lvalue, rvalue), T1 = int&, T2 = int
}

Is it the way to go? Is there any better way?

I have hardly any experience using cpp but it seems suspicious that I need to do such tricks to simply say "do not make copies of passed parameters".

I am using gcc 5.4.0 with -std=c++11.

-- UPDATE 1 --

I came up with the question when I was using streams library range(T&& lower, T&& upper) method which takes both T&& parameters. I can pass both lvalue or rvalue parameters to the function but it makes me unable to pass for example 0 and some_var as parameters. Whatever is the author's reasons for the function taking T&& parameters I was wondering if there is a way to extend the declaration to take mixed lvalue/rvalue parameters without sacrificing whatever the author wanted to achieve.

-- UPDATE 2 --

If the parameter is read-only const & can be used (@RichardCritten).

When you want to modify/return the parameter without copying you can use either templates or @Yakk solution.

@Yakk solution seems better when you declare a function that takes multiple parameters.

For example, if a function takes two int l/r-value reference parameters and returns int reference using templates leads to a messy signature.

template<typename T1, typename T2>
int& foo(T1 &&a, T2 &&b) {
  a += b;
  return a;
}

While @Yakk solution gives a pretty elegant one.

int& foo(any_ref<int> a, any_ref<int> b) {
  a += b;
  return a;
}
Community
  • 1
  • 1
Marcin Król
  • 1,555
  • 2
  • 16
  • 31
  • 2
    What's the point of the exercise? Why can't you have a function taking its parameter the old-fashioned way, by value or by const reference? – Igor Tandetnik Feb 06 '17 at 01:51
  • 3
    If you have no need to change the value of parameter just use `T const &` – Richard Critten Feb 06 '17 at 01:51
  • 2
    This question is usually associated with an XY-problem , perhaps you could describe in more detail the situation that caused you to ask about this – M.M Feb 06 '17 at 02:02
  • @IgorTandetnik @M.M I came up with this question when I was using [streams](https://github.com/jscheiny/Streams/blob/master/source/Stream.h) library `range(T&& lower, T&& upper)` method which takes both rvalue reference parameters which makes me unable to pass for example `0` and `some_var` as parameters. – Marcin Król Feb 06 '17 at 02:03
  • @RichardCritten I guess my question makes more sense when I want to modify or return the parameter without copying. – Marcin Król Feb 06 '17 at 02:35
  • Possible duplicate of [Function that accepts both lvalue and rvalue arguments](http://stackoverflow.com/questions/17644133/function-that-accepts-both-lvalue-and-rvalue-arguments) – yeputons Feb 06 '17 at 02:37
  • 1
    The code you link to for rationale uses universal references. This is very different to what's actually in your question regarding `int&&` which is not a universal reference. `T&&` (where T is a template parameter) binds to lvalues, but `int&&` does not. The stuff in the update should be a separate question to everything that came before (i.e. how can I modify this `range` function template to accept `(0, some_var)`) – M.M Feb 06 '17 at 03:25
  • @M.M I believe that knowing how to declare a function that can take either lvalue or rvalue reference is enough to extend the `range` function. @Yakk solution while not perfect seems to do the job. – Marcin Król Feb 06 '17 at 03:45

1 Answers1

1
template<class T>
struct any_ref{
  T& t;
  any_ref(T&in):t(in){}
  any_ref(T&&in):any_ref(in){}
  any_ref(any_ref const&)=default;
  any_ref& operator=(any_ref const&)=default;
  ~any_ref()=default;

  operator T&()const&{return t;}
  operator T&&()&&{return std::move(t);}// maybe
  T& get()const{return *this;}
};

Then:

void foo(any_ref<int>a, any_ref<int>b);

is not a template function. The body can do a int&a=a_arg; if it wants real references.

But really, just take int& if you want a "maybe-out" parameter. Callers who want to pass an rvalue as a lvalue can write:

templare<class T>
T& as_lvalue(T&&t){return t;}

and cast their rvalueness away.

If the parameter is a read only reference, use const&.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • If only you could get rid of the `get` function. Anyway, it does everything I want it to do. I wonder why the standard does not define a proper tool to do it. – Marcin Król Feb 06 '17 at 02:49
  • @MarcinKról because `as_lvalue` is probably a better way to do it -- mark up the rvalue as an lvalue at the call-site. It is exceedingly rare you want to have a reference parameter that can be modified that should bind to a temporary; I've rarely seen a situation where it wouldn't have been better to have a in parameter and a distinct (ignorable) out-parameter (as part of the return value). I've seen it, but it is rare, and in those rare cases, `as_lvalue` does fine. – Yakk - Adam Nevraumont Feb 06 '17 at 05:24