Barry
Barry

Reputation: 304182

Accept lvalue ref or rvalue ref

I want to write some functions that take an Object as one of their arguments, whether by lvalue or rvalue ref doesn't matter - but definitely not by value and definitely only an Object. It seems like I have two options for this:

void foo(Object& o) {
    // stuff
}

void foo(Object&& o) { foo(o); } // this is fine for my use-case

Or using universal references:

template <typename T, typename U>
using decays_to = typename std::is_same<std::decay_t<T>, U>::type;

template <typename T>
std::enable_if_t<decays_to<T, Object>::value>
foo(T&& o) 
{
    // same stuff as before
}

But the first option involves writing twice as many functions as I need, and the second option involves writing a bunch of template stuff which is seems like overkill to me (I kind of read that as accept anything for o - oh just kidding, really just an Object).

Is there a better way to solve this or am I just pretty much stuck with whichever one of these I feel less meh about?

Upvotes: 3

Views: 213

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275976

There is a difference between your two implementations. The first will admit anything convertible to Object, Object&& or Object&. The second will only admit Object and things that inherit from it in rvalue or lvalue form (and reject const Objects, much like the first).

We can add a helper object:

template<class T>
struct l_or_r_value {
  T& t;
  l_or_r_value( T&& t_ ):t(t_) {}
  l_or_r_value( T& t_ ):t(t_) {}
  operator T&(){ return t; }
  T* operator->(){ return &t; }
  T& operator*(){ return t; }
  T& get(){ return t; }
};

then we can write:

void foo(l_or_r_value<Object> o)

and we get behavior that is very close to your second solution, without the template mumbo jumbo at point of call. You do have to access *o and o-> or do a Object& o = o_; to get at the raw reference.

It is not like the first solution, because C++ does not chain two user-defined conversions.

The concepts proposal would add the ability to say "I take anything here, so long as it is an Object" with a more terse syntax.

Another approach would be to just take Object&, and use:

tepmlate<class T>
T& lvalue( T&& t) { return t; }

to convert rvalues to lvalues when you need to (you could also call it unmove to be cute)

Upvotes: 6

eerorika
eerorika

Reputation: 238491

The typical way to accept both lvalues and rvalues is to make a function that takes a const reference. That means you can't call non-const functions on the object, but if you want to pass rvalues such as temporaries, then calling non-const functions wouldn't necesarily make much sense anyway.

Upvotes: 4

Related Questions