3

So I have the following function:

void scan(std::istream& is, Handler& h);

I want to call it in different ways, like:

scan(std::cin, Handler());
scan(std::ifstream("myfile"), myhandler);

The compiler complains about std::ifstream("myfile") and Handler() of being rvalues being passed as non-const references, so the complaint is legitimate, but what can I do?

  1. Neither function parameters cannot be const (istream is modified while read and the handler changes its state during callbacks).
  2. If I change the parameter types to rvalue references (&&) then I will not be able to pass std::cin and sometimes I really care about the final state of myhandler thus I cannot apply std::move on them neither.
  3. In principle I could make the parameters as universal references via template or auto&& type deduction and thus overload this function for all possible combinations of lvalue and rvalue references, but I have no intention of overloading this function for other types than I have already specified.

Are there any other options?

Somehow this whole move semantics got in the way in such a trivial example.

mariusm
  • 1,483
  • 1
  • 11
  • 26
  • 3
    `std::ifstream("myfile")` is a temporary value, why not simply create a variable for it? – πάντα ῥεῖ Feb 16 '16 at 17:50
  • I know, it's temporary and thus rvalue, but can I convert it into lvalue ref? – mariusm Feb 16 '16 at 17:51
  • Does `scan` hold references/pointers to the stream objects after it returns? If not, you can do `std::ifstream("myfile") >> std::skipws` to get an lvalue reference to the temporary. – David G Feb 16 '16 at 17:51
  • 1
    This has nothing to do with move semantics. – Edward Strange Feb 16 '16 at 17:56
  • @0x499602D2 I see what you did there, but `>>` operator modifies the state of the stream, also the same trick is not applicable to `Handler()`. For me it's a language issue: suddenly I am forced to declare some temporary variables just to hold the references... – mariusm Feb 16 '16 at 17:56
  • @CrazyEddie if I try to get away with `&&` (i.e. let it consume both arguments) then it is. – mariusm Feb 16 '16 at 18:01
  • 1
    `templateT& lvalue_ref(T&& x){return x;}` then later - `scan(lvalue_ref(ifstream()), lvalue_ref(Handler()))` – David G Feb 16 '16 at 18:01
  • @πάνταῥεῖ I have to add that some of the calls are inside `switch`-`cases`, so if I declare a new variable in that scope, then compiler complains that it crosses the initialization. – mariusm Feb 16 '16 at 18:03
  • 1
    @mariusm That's a valid reason for using a pointer instead of a reference. Also you can create variables in a `switch`/`case` block putting them in a scope block (`{}`). – πάντα ῥεῖ Feb 16 '16 at 18:04
  • @0x499602D2 your `lvalue_ref` template seems the solution I was looking for. Strange that it is not part of `std::` :-) – mariusm Feb 16 '16 at 18:26
  • 1
    Why won't you [constrain](http://coliru.stacked-crooked.com/a/7fb7bc44ffabf9f6) a function template? – Piotr Skotnicki Feb 16 '16 at 18:36
  • @PiotrSkotnicki that's a lot of tedious code, I was hoping for simpler solution. – mariusm Feb 16 '16 at 19:22

1 Answers1

7

To convert an rvalue to an lvalue, you can use this lvalue helper function:

template<class T>
T& lvalue_ref(T&& x) { return x; }

And then the call becomes:

scan(lvalue_ref(std::ifstream("myfile")), lvalue_ref(Handler()));

This is safe as the temporaries (the ifstream and Handler) aren't destructed until the end of the full expression. However, note that these are lvalue references to temporaries and as such you must use caution when deciding to use this method. I'm assuming the scan() doesn't hold references/pointers to the arguments after it returns.

For example, do not use it like this:

int& x = lvalue_ref(5);
std::cout << x; // temporary is destructed, therefore Undefined Behavior

Just make sure the lifetime of the returned reference corresponds with the lifetime of the temporary, and you'll be fine.

David G
  • 94,763
  • 41
  • 167
  • 253