3

What is a good, safe way to extract a specific number of characters from a std::basic_istream and store it in a std::string?

In the following program I use a char[] to eventually obtain result, but I would like to avoid the POD types and ensure something safer and more maintainable:

#include <sstream>
#include <string>
#include <iostream>
#include <exception>

int main()
{
    std::stringstream inss{std::string{R"(some/path/to/a/file/is/stored/in/50/chars         Other data starts here.)"}};
    char arr[50]{};
    if (!inss.read(arr,50))
        throw std::runtime_error("Could not read enough characters.\n");

    //std::string result{arr}; // Will probably copy past the end of arr
    std::string result{arr,arr+50};

    std::cout << "Path is: " << result << '\n';
    std::cout << "stringstream still has: " << inss.str() << '\n';

    return 0;
}

Alternatives:

  • Convert entire stream to a string up front: std::string{inss.c_str()}
    • This seems wasteful as it would make a copy of the entire stream.
  • Write a template function to accept the char[]
    • This would still use an intermediate POD array.
  • Use std::basic_istream::get in a loop to read the required number of characters together with std::basic_string::push_back
    • The loop seems a bit unwieldy, but it does avoid the array.
wally
  • 10,717
  • 5
  • 39
  • 72
  • Possible duplicate: [How to get characters out of stringstream without copy?](http://stackoverflow.com/questions/28663075/how-to-get-characters-out-of-stringstream-without-copy) – NathanOliver Jul 06 '16 at 14:12
  • Are you guaranteed to have the filename be 50 chars or less? – Nic Jul 06 '16 at 14:14
  • @QPaysTaxes The file format has the first 50 characters assigned to the filename. So guaranteed for as long as the file format stays the same. The risk is that the file format changes and then the code has to change. – wally Jul 06 '16 at 14:17

1 Answers1

3

Just read it directly into the result string.

#include <sstream>
#include <string>
#include <iostream>
#include <exception>

int main()
{
    std::stringstream inss{std::string{R"(some/path/to/a/file/is/stored/in/50/chars         Other data starts here.)"}};
    std::string result(50, '\0');

    if (!inss.read(&result[0], result.size()))
        throw std::runtime_error("Could not read enough characters.\n");

    std::cout << "Path is: " << result << '\n';
    std::cout << "stringstream still has: " << inss.str() << '\n';

    return 0;
}

Since C++11, the following guarantee about the memory layout of the std::string (from cppreference).

The elements of a basic_string are stored contiguously, that is, for a basic_string s, &*(s.begin() + n) == &*s.begin() + n for any n in [0, s.size()), or, equivalently, a pointer to s[0] can be passed to functions that expect a pointer to the first element of a CharT[] array. (since C++11)

James Adkison
  • 9,412
  • 2
  • 29
  • 43
  • 2
    @jaggedSpire That's if you access `pos` when it is equal to the value returned by the `size` function. In this example `0` is used for `pos` whereas `50` is returned for by `size` and there is no undefined behavior. – James Adkison Jul 06 '16 at 14:27
  • This is exactly the answer I was looking for. Now it seems so obvious... :) – wally Jul 06 '16 at 14:29
  • @JamesAdkison yep, caught that after I posted. Sorry. – jaggedSpire Jul 06 '16 at 14:32