Is there a way to write a function in C++ that accepts both lvalue and rvalue arguments, without making it a template?
For example, suppose I write a function print_stream
that reads from an istream
and prints the data that was read to the screen, or something.
I think it's reasonable to call print_stream
like this:
fstream file{"filename"}; print_stream(file);
as well as like this:
print_stream(fstream{"filename"});
But how do I declare print_stream
so that both uses work?
If I declare it as
void print_stream(istream& is);
then the second use won't compile because an rvalue will not bind to a non-const lvalue reference.
If I declare it as
void print_stream(istream&& is);
then the first use won't compile because an lvalue will not bind to an rvalue reference.
If I declare it as
void print_stream(const istream& is);
then the function's implementation won't compile because you can't read from a const istream
.
I can't make the function a template and use a "universal reference", because its implementation needs to be separately compiled.
I could provide two overloads:
void print_stream(istream& is); void print_stream(istream&& is);
and have the second call the first, but that seems like a lot of unnecessary boilerplate, and I would find it very unfortunate to have to do that every time I write a function with semantics like this.
Is there something better I can do?
By overloading a function to take a const lvalue reference or an rvalue reference, you can write code that distinguishes between non-modifiable objects (lvalues) and modifiable temporary values (rvalues). You can pass an object to a function that takes an rvalue reference unless the object is marked as const .
By default, the compiler cannot bind a non-const or volatile lvalue reference to an rvalue.
For example, An assignment expects an lvalue as its left operand, so the following is valid: int i = 10; But this is not: int i; 10 = i; This is because i has an address in memory and is a lvalue. While 10 doesn't have an identifiable memory location and hence is an rvalue.
Typically rvalues are temporary objects that exist on the stack as the result of a function call or other operation. Returning a value from a function will turn that value into an rvalue. Once you call return on an object, the name of the object does not exist anymore (it goes out of scope), so it becomes an rvalue.
There is not much of a sane choice other than offering two overloads or making your function a template, I would say.
If you really, really need an (ugly) alternative, then I guess the only (insane) thing you can do is to have your function accept a const&
, with a pre-condition saying that you cannot pass an object of a const
-qualified type to it (you don't want to support that anyway). The function would then be allowed to cast away the const
ness of the reference.
But I'd personally write two overloads and define one in terms of the other, so you do duplicate the declaration, but not the definition:
void foo(X& x) { // Here goes the stuff... } void foo(X&& x) { foo(x); }
Another rather ugly alternative is to make the function a template and explicitly instantiate both versions:
template<typename T> void print(T&&) { /* ... */ } template void print<istream&>(istream&); template void print<istream&&>(istream&&);
This can be compiled separately. The client code only needs the declaration of the template.
I'd personaly just stick with what Andy Prowl suggests, though.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With