2
#include <fstream>
#include <string>
#include <vector>

int main() {
    auto fin  = std::ifstream("tmp.txt");
    auto pos  = std::istream_iterator<std::string>(fin);
    auto last = std::istream_iterator<std::string>{};
    auto strs = std::vector<std::string>{};

    for (; pos != last; ++pos) {
        // Always COPY rather than MOVE the string here!
        strs.emplace_back(std::move(*pos)); 
    }
}

Note that strs.emplace_back(std::move(*pos)) will always COPY rather than MOVE the string, even if std::move(*pos).

This is due to std::istream_iterator<std::string>::operator* is defined as follows:

const T& operator*() const;

Which means we cannot move the cached objects even if the iterator is single-pass!

If the C++ standard had defined it as follows

T& operator*() const;

Then, we could

  • use std::istream_iterator<std::string> if we are sure to move each string exactly once, or,
  • use std::istream_iterator<std::string const> if we would reference the same iterator more than once.

Why did the C++ standard not do so since C++11 has introduced move-semantics? What's the rationale behind the decision?

3
  • 1
    On the contrary, views::istream solves your issue. Commented Jul 23 at 12:33
  • Or you read the whole file into memory once, and then split up your file into string_views like this : godbolt.org/z/xajj6jrrG. This doesn't answer your question on why, but it provides a how it could be done (assuming you don't have access to C++20/ranges) Commented Jul 23 at 12:53
  • @康桓瑋, yeah, std::views::istream is all I need! You can make your comment an answer. Commented Jul 23 at 13:32

2 Answers 2

7

The design of std::istream_iterator<std::string>::operator* predates move semantics.

From cppreference:

std::istream_iterator is a single-pass input iterator that reads successive objects of type T from the std::basic_istream object for which it was constructed, by calling the appropriate operator>>. The actual read operation is performed when the iterator is incremented, not when it is dereferenced. The first object is read when the iterator is constructed. Dereferencing only returns a copy of the most recently read object.

Just dereferencing does not invalidate the reference, only incrementing does. Hence, in principle operator* could return a non constant reference. Though, without move semantics there was no advantage of returning a non-const reference, because anyhow you were expected to copy the string before incrementing the iterator further.

Sign up to request clarification or add additional context in comments.

Comments

5

As another answer says, istream_iterator existed before C++11 introduced move semantics.

However, for C++20 ranges::istream_view, its iterator's operator* always returns Val&, so you can move the extracted element from inside the istream_view to outside:

auto fin  = std::ifstream("tmp.txt");
auto strs = std::views::istream<std::string>(fin)
          | std::views::as_rvalue
          | std::ranges::to<std::vector>();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.