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.