Zereges
Zereges

Reputation: 5209

C++ passing istream the in conditional operator to the function

I am quite confused about behaviour of conditional operator. Suppose function like this

SomeData read_from_stream(istream& stream);

The function itself is returning some data pack, we want to catch.

if (file_name != "")
    SomeData data = read_from_stream(ifstream(file_name)); // calling cpy/move constructor
else
    SomeData data = read_from_stream(cin);
// data out of scope :(

Ok then, put SomeData out of if-else

SomeData data; // calling default constructor :(
if (file_name != "")
    data = read_from_stream(ifstream(file_name));
else
    data = read_from_stream(cin);

Default constructor might not even exist. Ok then, another idea.

SomeData data = read_from_stream((file_name != "") ? ifstream(file_name) : cin);

error C2248: 'std::basic_istream<char,std::char_traits<char>>::basic_istream': cannot access protected member declared in class 'std::basic_istream<char,std::char_traits<char>>'

Well, I've heard something about streams being not-copyable, but I am not copying anything, am I?

EDIT:

I came up with this

auto lmbd = [&file_name]() -> istream& {
    if (file_name != "")
        return ifstream(file_name); // returning reference to temporary :(
    else
        return cin;
};
SomeData data = read_from_stream(lmbd());

This compiles, but it crashes during run time when trying to std::getline(stream, str); if stream is ifstream set up using filename.

Upvotes: 0

Views: 614

Answers (1)

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385274

I've heard something about streams being not-copyable, but I am not copying anything, am I?

Yeah, unfortunately, you are.

A conversion is performed from std::ifstream to std::istream before any reference binding occurs:

[C++11: 5.16/3]: [..] If E2 is an rvalue or if neither of the conversions above can be done and at least one of the operands has (possibly cv-qualified) class type:

  • if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to a prvalue of type T2 by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.

[..]

There are all the standard ways to mitigate this (some of which you've already explored). A really nasty one that works is to add an overload to read_from_stream that takes an rvalue reference, and cast both expression operands to the same type:

#include <fstream>
#include <iostream>

void f(std::istream&&);

int main()
{
    f(
        false
        ? (std::istream&&) std::ifstream("/tmp")
        : (std::istream&&) std::cin
    );
}

(see it compiling)

As compared to my testcased version of your original code:

#include <fstream>
#include <iostream>

void f(std::istream&);

int main()
{
    f(
        false
        ? std::ifstream("/tmp")
        : std::cin
    );
}

("error: use of deleted function 'std::basic_istream<char>::basic_istream(const std::basic_istream<char>&)'")

Upvotes: 1

Related Questions