Home > Mobile >  How to make begin and end functions rvalue qualified
How to make begin and end functions rvalue qualified

Time:11-29

I am writing a file parser. Since the user can pass through the file only once I want the iterator to be accessible only from rvalue reference.

MyParser parser("/path/to/file");
for(auto it : std::move(parser)){
   // example
   for(auto & [key, value]: it){
      std::court << key << '\t' << value << std::endl;
   }
}

So, I did the following

class MyParser{

public:
  MaParser(const std::string &path): begin_iterator_(path){}


  //  ====== Begin   iterator class ==============
  class iterator{
    public:
      typedef std::map<std::string, std::string> value_type;

      // Ctor
      iterator(const std::string & path):{
        // Initialize
      }

      // Default ctor
      iterator() = default;
      
      // Move ctor
      iterator(iterator && other){
        istream_ = std::move(other.istream_);
        values_ = std::move(other.value_);
      }

      iterator & operator=(iterator && other){
        istream_ = std::move(other.istream_);
        values_ = std::move(other.value_);
        return *this;
      }

      void operator  (){// definition}

      value_type & operator*(){
        return value_;
      }    
      
      bool operator==(const iterator & other) const {
        return false; // logic here
      }

      bool operator!=(const iterator & other) const {
        return !(*this == other);
      }

    private:
      std::unique_ptr<std::istream> istream_;
      value_type value_;        
  };
    //  ====== End   iterator class ==============

  iterator && begin() &&{
    return std::move(begin_iterator_);
  }

  iterator && end() && {
    return iterator();
  }
private:
  iterator begin_iterator_;
};

Now, I can iterate like this:

for(auto it = std::move(parser).begin(); it != myParser::iterator();   it){

}

but when I try to iterate according to the original intention I get an error

cannot convert 'this' pointer from 'MyParser' to 'MyParser &&'.

Q1: How do I solve this problem?

Q2: If instead of the begin_iterator in the function begin do

iterator && begin() &&{
    return iterator(path_); // Assuming we stored the path in a local variable.
  }

the program crashes on the iterator init auto it = std::move(parser).begin(); , when trying to move value_ in the move-constructor. Why?

CodePudding user response:

A range-based for is basically syntax sugar. Your loop of:

for (auto it : std::move(parser)) {
    statements;
}

is equivalent to:

{
    auto && __range = std::move(parser);
    for (auto __begin = __range.begin(), end = __range.end(); __begin != __end;   __begin) {
        auto it = *__begin;
        {
            statements;
        }
    }
}

So even though __range is an rvalue reference, __range.begin() still calls begin on an lvalue (the rvalue-ness isn't used).

There's no way around this, you cannot make it so that only rvalues can be iterated over in a range-for. For reference, std::ranges::istream_view does a similar thing and the only thing it does for safety is make its iterator move-only, so what you already have should be enough.


For your second question, if you return a temporary from iterator && begin() &&, the temporary is destroyed before the function returns and you get a dangling reference. Your current end function suffers from the same problem. Simply return by value iterator begin() &&. The move from the temporary should be elided.

  • Related