c++standardsapi-designmove-semanticsistream-iterator

Why can I not efficiently move the strings when using std::istream_iterator<std::string>?


#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?


Solution

  • 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>();