Structure-bound std::tuple breaks std::stringstream

2

Consider the following code:

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

auto read_data(std::ifstream& training_file) {
    if (!training_file) {
        throw std::runtime_error{"Error: could not open one or more files"};
    }

    std::stringstream training{};
    training << training_file.rdbuf();

    std::cout << training.str() << '\n';

    return std::tie(training);
}

int main() {
    std::ifstream input{"input.txt"};

    auto [train] = read_data(input);

    std::cout << train.str() << '\n';
    std::cout << "x" << '\n';
}

And ignore the fact that I am returning a single element with std::tie (originally I am tieing two std::stringstream objects, but that's not needed for the MCVE).

The input.txt file looks like this:

0,0,0,0,
0,0,0,0,

Note - the is no line break after the second line.

The unexpected output of this program is:

0,0,0,0,
0,0,0,0,
É$~      Ź     ,
x

Obviously, the É$~ Ź , part should not have been there.

Notice that I am outputting the very same file contents. I am clueless where did the unexpected part come from.

It gets even stranger when playing with the code. If I comment out the std::cout << train.str() << '\n'; in main() and duplicate the std::cout << training.str() << '\n'; line in read_data() function, the output is as expected:

0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0,
x

So it's not causaed by calling std::stringstream::str twice. It must be caused by the usage of the returned value.

What's more? Executing the std::cout << train.str() << '\n'; line from main() twice results in program termination with code 3.

GDB reports:

gdb: unknown target exception 0x80000001 at 0x7ff909e845c0
Thread 1 received signal ?, Unknown signal.
0x00007ff909e845c0 in ?? ()

But it still does not end here. If I change the file content to contain either:

0 0

or

0 0 0 0
0 0 0 0

the output is again as expected (using the original code - one cout in read_data() and one in main().

To make sure I wasn't being tricked by some non-printable character that snuck into my file, I used PowerShell to output its Hex representation, which resulted in the following sequence regarding the original file content:

30 20 30 20 30 20 30 0D 0A 30 20 30 20 30 20 30

As you can see, nothing except for 0s, spaces and carriage-return + line-feed is in my file.

Any idea why this could be happening? For the full information, I am using GCC 8.2.0 from MinGW.

c++
stringstream
stdtuple
asked on Stack Overflow Mar 17, 2019 by Fureeish • edited Mar 17, 2019 by Fureeish

1 Answer

4

This is a local:

std::stringstream training{};

This returns a reference to said local, wrapped in a tuple:

return std::tie(training);

So auto [train] = ...; initializes the name train to be a dangling reference. The behavior of the program is undefined.


If you need to return two streams ( or more ) then just predaclare them in the tuple/array/custom aggregate of your choice:

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

auto read_data() {
    std::array<std::stringstream,2> trainings{
        std::stringstream{},
        std::stringstream{}
    };

    return trainings;
}

int main() {

    auto train = read_data();

    std::cout << train[1].str() << '\n';
    std::cout << "x" << '\n';
}

Live Code

Copy elision guarantees the extra object will either be removed altogether or move constructed into place.


User contributions licensed under CC BY-SA 3.0