3

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?

3
  • 3
    Would void foo(Object const& o) suffice? It will accept both r-value and l-value, but it is const. Commented Oct 28, 2014 at 14:36
  • 1
    @Niall wow. it totally would. sometimes, I apparently massively overthink things. Commented Oct 28, 2014 at 14:45
  • std::remove_const_t<std::remove_reference_t<T>> would be a better choice than std::decay_t<T> since decay_t has the side effect of performing function-to-pointer and array-to-pointer conversions. Commented Oct 28, 2014 at 17:10

2 Answers 2

6

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)

Sign up to request clarification or add additional context in comments.

3 Comments

I really like this answer - and will probably use l_or_r_value somewhere - but I'm accepting the other one just because it's more straightforward for my use-case.
@Barry ah! Sorry, I figured you actually wanted to modify the value in the function, and rejecting const objects was on purpose.
Nope, was rejecting them based solely on a problem that exists between the keyboard and the chair.
4

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.