Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can you pass an std::istream into a function in a way that allows to pass temporaries?

Tags:

c++

istream

I am trying to create a constructor to load a resource from any istream given to it. I cannot seem to figure out the best way to pass the istream parameter into a constructor.

Loader::Loader(istream stream);

This one is obviosly bad due to object slicing, so no option.

Loader::Loader(istream& stream);

This is what I am using now and seems fairly alright. It has one significant issue though - you can't give it a temporary since temporaries cannot bind to non-const references! For example, the following won't work:

Container():
  mLoader(ifstream("path/file.txt", ios::binary)
{
}

This is rather a limitation since I am now forced to store the ifstream as a member variable of Container just to extend its lifetime.

Since the problem is with non-const references, one could have though of this:

Loader::Loader(const istream& stream);

But since .seek() etc are non-const, this is not an option either...

So, how can this problem be solved in a neat way?

like image 355
Tobias Avatar asked Dec 09 '15 14:12

Tobias


2 Answers

if your compiler is c++11 or better you can simply provide a version of the constructor that takes the istream as an r-value reference:

void Loader::Loader(std::istream&& is)

quick example:

#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>

struct Loader
{
    Loader(std::istream& is)
    {
        read(is);
    }

    Loader(std::istream&& is)
    {
        read(is);
    }

    void read(std::istream& is)
    {
        is >> std::quoted(x);
        is >> std::quoted(y);
    }

    std::string x, y;
};

std::ostream& operator<<(std::ostream& os, const Loader& l)
{
    os << "x = " << l.x;
    os << ", y = " << l.y;
    return os;
}


auto main() -> int
{
    using namespace std;

    Loader l(istringstream(R"text("donkey" "horse")text"));
    cout << l << endl;

    istringstream not_temp(R"text("apple" "banana")text");
    Loader l2(not_temp);
    cout << l2 << endl;

    return 0;
}

expected output:

x = donkey, y = horse
x = apple, y = banana
like image 68
Richard Hodges Avatar answered Oct 22 '22 13:10

Richard Hodges


The example

Container():
  mLoader(ifstream("path/file.txt", ios::binary)
{
}

… is ill-conceived, because the lifetime of the temporary would be just the constructor call. Then you'd be left with a dangling reference. Using that reference would be Undefined Behavior.

However, technically, in order to be able to pass a temporary to a reference-to-non-const formal argument, just define a little helper like this:

template< class Type >
auto temp_ref( Type&& o )
    -> Type&
{ return o; }

Then you can call e.g. foo( temp_ref( Whatever() ) ), but just keep in mind that the lifetime of that temporary is the full-expression that it occurs in.

like image 2
Cheers and hth. - Alf Avatar answered Oct 22 '22 15:10

Cheers and hth. - Alf