简体   繁体   中英

rvalue or lvalue (const) reference parameter

I want to pass a parameter(s) (of some concrete type, say int ) to the member function by r- or l- value (const) reference. My solution is:

#include <type_traits>
#include <utility>

struct F
{
    using desired_parameter_type = int;

    template< typename X, typename = typename std::enable_if< std::is_same< typename std::decay< X >::type, desired_parameter_type >::value >::type >
    void operator () (X && x) const
    {
        // or even static_assert(std::is_same< typename std::decay< X >::type, desired_parameter_type >::value, "");
        std::forward< X >(x); // something useful
    }
};

Another exaple is here http://pastebin.com/9kgHmsVC .

But it is too verbose. How to do it in a simpler way?

Maybe I should use the superposition of std::remove_reference and std::remove_const instead of std::decay , but there is just a simplification here.

If I understand your question correctly, you wish to have a single function whose parameter is either an rvalue reference (in case an rvalue is provided) or an lvalue reference to const (in case an lvalue is provided).

But what would this function do? Well, since it must be able to handle both cases, including the case where an lvalue is provided, it cannot modify its input (at least not the one bound to the x parameter) - if it did, it would violate the semantics of the const reference.

But then again, if it cannot alter the state of the parameter, there is no reason for allowing an rvalue reference: rather let x be an lvalue-reference to const all the time. lvalue references to const can bind to rvalues, so you will be allowed to pass both rvalues and lvalues.

If the semantics of the function is different based on what is passed, then I would say it makes more sense to write two such functions: one that takes an rvalue reference and one that takes an lvalue reference to const .

As Andy has mentioned, the important thing here is what you could actually do inside your function which would make sense.

  • You can forward arguments to another function. In this case, using a template doesn't matter, because if the wrong parameter type is given it will still produce a compile error (wrong types sent to second function). You can use template <typename T> blah (T && x) .
  • Anything else would require you to write different code depending on whether it is an r- or l- value reference, so you'll need to write 2 functions anyway: blah (const int & x) and blah (int && x) .

I assume you must be attempting the first option and you're trying to make any potential compiler errors more user-friendly. Well, I'd say it's not worth it; the programmer will still see a "called by…" list in the output of any decent compiler.

Actually, this is a very good question. So far I have also been using the universal reference trick plus the enable_if hammer. Here I present a solution that doesn't make use of templates and uses lvalue cast as an alternative.

What follows is a real example where the situation arises using the known example of inplace ofstream usage that is not possible (or very hard) in C++98 (I use ostringstream in the example to make it more clear).

First you will see a function on lvalue reference as often seen in C++98.

#include<iostream>
#include<sstream>
struct A{int impl_;};

std::ostringstream& operator<<(std::ostringstream& oss, A const& a){
    oss << "A(" << a.impl_ << ")"; // possibly much longer code.
    return oss;
}
// naive C++11 rvalue overload without using templates
std::ostringstream& operator<<(std::ostringstream&& oss, A const& a){
    oss << "A(" << a.impl_ << ")"; // ok, but there is code repetition.
    return oss;
}

int main() {

    A a{2};
    {// C++98 way
        std::ostringstream oss;
        oss << a;
        std::cout << oss.str() << std::endl; // prints "A(2)", ok"
    }
    {// possible with C++11, because of the rvalue overload
        std::cout << (std::ostringstream() << a).str() << std::endl; //prints "A(2)", ok
    }
}

As you can see in C++11 we can achieve what we can't in C++98. That is to make use of the ostringstream (or ofstream ) inplace. Now comes the OP question, the two overloads look very similar, can both be joined in one?

One option is to use universal reference ( Ostream&& ), and optionally with enable_if to constrain the type. Not very elegant.

What I found by using this "real world" example is that if want to use the same code for lvalue ref and rvalue ref is because probably you can convert one to the other!

std::ostringstream& operator<<(std::ostringstream&& oss, A const& a){
    return operator<<(oss, a);
}

This looks like an infinitely recursive function, but it is not because oss is an lvalue reference (yes, it is an lvalue reference because it has a name). So it will call the other overload.

You still have to write two functions but one has a code that you don't have to maintain.

In summary, if "it makes sense"© to apply a function both to a (non const) lvalue reference and rvalue that also means that you can convert the rvalue into an lvalue and therefore you forward to a single function. Note that the "it makes sense" depends in the context and the meaning of the intended code, and it is something that we have to "tell" the compiler by explictly calling the lvalue overload.

I am not saying that this is better than using universal reference, I say it is an alternative and arguably the intention is more clear.

Editable code here: http://ideone.com/XSxsvY . (Feedback is welcomed)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM