Reputation: 304182
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
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 Object
s, 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
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