7

I want to have a variable of type istream which can hold either the contents of a file or a string. The idea is that if no file was specified, the variable of type istream would be assigned with a string.

std::ifstream file(this->_path)

and

std::istringstream iss(stringSomething);

to

std::istream is

I've tried just assigning them to the istream variable like I would with other objects that inherit from the same base class, but that didn't work.

How to assign istringstream and ifstream to an istream variable?

Remy Lebeau
  • 454,445
  • 28
  • 366
  • 620
oddRaven
  • 552
  • 5
  • 18

6 Answers6

7

Base class pointers can point to derived class data. std::istringstream and std::ifstream both derived from std::istream, so we can do:

//Note that std::unique_ptr is better that raw pointers
std::unique_ptr<std::istream> stream;

//stream holds a file stream
stream = std::make_unique<std::ifstream>(std::ifstream{ this->_path });

//stream holds a string
stream = std::make_unique<std::istringstream>(std::istringstream{});

Now you just have to extract the content using

std::string s;
(*stream) >> s;
Rakete1111
  • 42,521
  • 11
  • 108
  • 141
  • 1
    I would probably use `std::unique_ptr stream_owner;` and then `std::istream& stream = *stream_owner;`, allowing the familiar `stream >> s` syntax. – Mooing Duck Aug 05 '16 at 22:02
  • 2
    The `make_unique()` calls can be simplified a little: `stream = std::make_unique(_path);` and `stream = std::make_unique();` Also note that `std::unique_ptr` is new in C++11, but `std::make_unique` is new in C++14. – Remy Lebeau Aug 06 '16 at 05:10
4

You can't assign to a std::istream but you can bind to a reference like this:

#include <string>
#include <sstream>
#include <fstream>
#include <iostream>

std::istringstream test_data(R"~(

some test data here
instead of in an external
file.

)~");

int main(int, char* argv[])
{
    // if we have a parameter use it
    std::string filename = argv[1] ? argv[1] : "";

    std::ifstream ifs;

    // try to open a file if we have a filename
    if(!filename.empty())
        ifs.open(filename);

    // This will ONLY fail if we tried to open a file
    // because the filename was not empty    
    if(!ifs)
    {
        std::cerr << "Error opening file: " << filename << '\n';
        return EXIT_FAILURE;
    }

    // if we have an open file bind to it else bind to test_data
    std::istream& is = ifs.is_open() ? static_cast<std::istream&>(ifs) : test_data;

    // use is here
    for(std::string word; is >> word;)
    {
        std::reverse(word.begin(), word.end());
        std::cout << word << '\n';
    }
}
Galik
  • 42,526
  • 3
  • 76
  • 100
  • 1
    This is exactly what I was looking for: a complete example for choosing a default stream (e.g. `std::cin`) if a file was not supplied, and to assign it to a variable of type `std::istream`. – vallismortis Nov 18 '18 at 15:16
1

Take a page out of the standard library: don't assign a value; assign a reference. That's probably what you want anyway.

std::istringstream iss(stringSomething);
std::istream& input(iss);

Because streams carry a lot of state, copying them is fraught with semantic questions. Consider for example what tellg should report in the copy after the original calls seekg. References by contrast answer the question transparently.

James K. Lowden
  • 6,629
  • 1
  • 13
  • 28
1

In C++, you cannot assign an object of type Child to a variable of type Parent, even if Child inherits from Parent. You can assign a pointer of type Child to a pointer of type Parent, however. You may want to consider dynamically allocating the objects.

Alessandro Power
  • 2,161
  • 1
  • 11
  • 37
1

In C++

std::istream is;

is an actual object, assigning to it will invoke the copy assignment operator which will copy the subobject of iss which is a std::istream into is and slice it. The example linked by LogicStuff will show that you need to assign a reference or pointer to iss like so:

std::istream &is_ref = iss;

The difference between values, references and pointers is fundamental to C++, I would advise getting a strong grasp of them.

Community
  • 1
  • 1
1stCLord
  • 854
  • 5
  • 14
1

std::istream can be constructed from a std::streambuf (basically the device that produces or consumes characters). All i/ostream objects have an associated std::streambuf and can be shared.

std::ifstream file(this->_path); 
std::istringstream iss("str in gSo met hing");

std::istream A(iss.rdbuf());   // shares the same buffer device with iss

std::string str;

//////////////
while(A >> str) std::cout << str << " | "; //read everything from stream (~> iss)
std::cout << std::endl;

A = std::move(file);
while(A >> str) std::cout << str << " | "; //read from file, using same stream (~> file)
Remy Lebeau
  • 454,445
  • 28
  • 366
  • 620
WhiZTiM
  • 19,970
  • 3
  • 36
  • 56
  • In the second case, shouldn't you call `A.rdbuf(file.rdbuf());` to share the buffer instead of transferring ownership? – Remy Lebeau Aug 06 '16 at 05:16