Reputation: 16721
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.
Upvotes: 6
Views: 2513
Reputation: 16262
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)
Upvotes: 1
Reputation: 46279
As Andy has mentioned, the important thing here is what you could actually do inside your function which would make sense.
template <typename T> blah (T && x)
.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.
Upvotes: 1
Reputation: 126452
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
.
Upvotes: 4