10

Is it safe and well-defined for the second argument to std::getline(std::istream&, std::string&) to be an lvalue referring to a moved-from std::string, and, if so, is that string restored from its moved-from state, so methods such as pop_back() can be safely invoked?

Put more simply, does writing to a string with getline() have equivalent semantics to assigning to that string?

Or more concretely, is the following (somewhat contrived) snippet well-defined and correct?

std::ifstream f("foo.txt");

std::vector<std::string> lines;

for (std::string s; std::getline(f, s); lines.push_back(std::move(s)))
        if (!s.empty() && s.back() == '\r')
                s.pop_back();

Optimized (-march=native -O3) builds of this snippet with g++ and clang++ appear to work as expected, but that is of course no guarantee.

I'd like to know if this is relying only on well-defined behavior according to the semantics of getline() in the C++11 standard, or, if not, if it's well-defined by a later edition of the standard, or, if not, if it's at least explicitly defined by any/all of the major implementations (G++, Clang++, Visual C++, Intel C++ Compiler).

NB: This is not a duplicate of previous questions asking whether it's safe to assign to a moved-from object (yes, if it's a trivial or STL type) because getline() is not the assignment operator.

Ray Hamel
  • 1,129
  • 4
  • 15
  • If you're not sure, how about changing it so that you `emplace_back()` a new string and then call `std::getline(f, lines.back())` instead? – SirGuy Oct 10 '17 at 18:41
  • 1
    This might answer your question: https://stackoverflow.com/a/12095473/969365 excerpt: _The object being in a 'valid' state means that all the requirements the standard specifies for the type still hold true. That means you can use any operation on a moved-from, standard library type for which the preconditions hold true._ If it is possible to use any operation on a moved-from standard library type, then std::getline should be safe to use as well. – simon Oct 10 '17 at 18:43
  • @SirGuy That's my fallback, but AFAICT it's hard to do in a non-ugly way in a loop. Safety trumps aesthetics, of course. – Ray Hamel Oct 10 '17 at 18:47
  • @gurka I *think* my code is OK for that reason, but it's unclear whether `getline()` is guaranteed only to invoke methods for which a moved-from string satisfies the preconditions. – Ray Hamel Oct 10 '17 at 18:54

3 Answers3

9

Your code is safe, but only because you are checking if the getline successfully stored something in s. If the sentry internal to getline failed to initialize, then nothing would be assigned to s and it would be left in the valid-but-unspecified state. As long as getline succeeds in setting s to a known state, you're good.

And the very first thing getline does after successful sentry construction is set s to a zero size (a known state).

More details below in GManNickG's comment.

Howard Hinnant
  • 179,402
  • 46
  • 391
  • 527
  • 2
    Beat me to it. In more detail, `getline` is specified to call `str.erase()`; if you look at the requirements for that, you will find it just requires `pos <= size()` which is trivially true because `pos` is 0 and `size()` is unsigned. (Also, `size()` has no preconditions.) So `getline` can safely do this on a moved-from string. – GManNickG Oct 10 '17 at 19:00
5

Is it safe and well-defined for the second argument to std::getline(std::istream&, std::string&) to be an lvalue referring to a moved-from std::string

Yes. Although it the moved-from string is left unspecified by the standard according to [string.cons]p2:

[...]. In the second form, str is left in a valid state with an unspecified value.

The string is still in a valid state, it is just unspecified, i.e. you can't know which state it is in (in may be filled with some random characters). You can still to pass it to std::getline, because the standard says in [string.io]p1 that str.erase() is called on the string, which empties it:

[...] calls str.erase() [...]

(if the stream is valid, but I'll assume that is in your case).

No matter what the unspecified state of the string is, it is emptied at the start of std::getline, so it doesn't matter what state it is in.

Rakete1111
  • 42,521
  • 11
  • 108
  • 141
  • 3
    "For example, std::string moved from state is just an empty string, nothing special." Is this guaranteed? Because this is more or less his question. After having moved from std::string it is valid but in a unspecified state. And the question is; is it OK to use a `std::string` which is "valid but in an _unspecified_ state" when using `std::getline`? – simon Oct 10 '17 at 18:40
  • 2
    The move constructor for a basic_string leaves the source "in a valid state with an unspecified value." This is different from guaranteeing it's an empty string. – GManNickG Oct 10 '17 at 18:44
  • @gurka Ah I get it. It is not directly guaranteed, but this is what most implementations would do. – Rakete1111 Oct 10 '17 at 18:52
  • @Rakete1111 I think most implementations switched or will switch to SSO and will not nullify zero short strings – RiaD Oct 14 '17 at 22:25
1

A move assignment or move c'tor should always leave the moved object in a valid state.

In case of a std::string, when you std::move it, it stays a valid but unspecified string. From libc++'s source:

// __str is a valid, but unspecified string. 
basic_string(basic_string&& __str) noexcept
  : _M_dataplus(_M_local_data(), std::move(__str._M_get_allocator()))

And since getline() only appends chars to the string, as long as it succeeds, the output will be as expected. From cplusplus.com:

Note that any content in str before the call is replaced by the newly extracted sequence.

Each extracted character is appended to the string as if its member push_back was called.

Community
  • 1
  • 1
Daniel Trugman
  • 6,100
  • 14
  • 35