在基于 C++ 范围的 for 循环中,begin() return 可以引用不可复制的迭代器吗?
In a C++ range-based for loop, can begin() return a reference to a non-copyable iterator?
更新:
感谢所有提交答案的人。
简而言之,答案是 begin()
和 end()
return 的“迭代器”必须是可复制的。
Artyer 提出了一个很好的解决方法:创建一个迭代器 class,其中包含对不可复制对象的引用(或者,指针)。下面是示例代码:
struct Element {};
struct Container {
Element element;
struct Iterator {
Container * c;
Iterator ( Container * c ) : c(c) {}
bool operator != ( const Iterator & end ) const { return c != end.c; }
void operator ++ () { c = nullptr; }
const Element & operator * () const { return c->element; }
};
Iterator begin () { return Iterator ( this ); }
Iterator end () { return Iterator ( nullptr ); }
};
#include <stdio.h>
int main () {
Container c;
printf ( "main %p\n", & c .element );
for ( const Element & e : c ) { printf ( "loop %p\n", & e ); }
return 0;
}
原题:
下面的 C++ 代码将无法编译(至少在 Ubuntu 20.04 上的 g++
版本 9.3.0 无法编译)。
错误信息是:
use of deleted function 'Iterator::Iterator(const Iterator&)'
根据错误,我得出 begin()
和 end()
编辑的“迭代器”return 必须可复制的结论是否正确?或者有什么方法可以使用 return 通过引用编辑的不可复制的迭代器?
struct Iterator {
Iterator () {}
// I want to prevent the copying of Iterators, so...
Iterator ( const Iterator & other ) = delete;
bool operator != ( const Iterator & other ) { return false; }
Iterator & operator ++ () { return * this; }
Iterator & operator * () { return * this; }
};
struct Container {
Iterator iterator;
Iterator & begin() { return iterator; }
Iterator & end() { return iterator; }
};
int main () {
Container container;
for ( const Iterator & iterator : container ) {}
// The above for loop causes the following compile time error:
// error: use of deleted function 'Iterator::Iterator(const Iterator&)'
return 0;
}
是的,迭代器必须是可复制的。这是因为范围迭代 is logically equivalent to the following code:
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
这是C++11版本。 C++17 和更高版本略有不同,但根本原因是相同的:__begin
和 __end
是 auto
,而不是 auto &
,或类似的东西.它们是 non-reference 类型。 “begin_expr”和“end_expr”,换句话说,就是最终调用自定义 begin()
和 [=] 的 begin
和 end
表达式18=].
即使您的 begin()
和 end()
returns 引用,它们也会被分配给 non-reference 类型,因此必须是可复制的。
请注意,即使情况并非如此,所示的实现也不是很有用,因为两个引用始终是同一个对象,因此开始和结束表达式最终将成为同一个对象,并且总是比较相等(希望如此)。
The range-based for statement
for ( init-statement(opt) for-range-declaration : for-range-initializer ) statement
is equivalent to
{
init-statement(opt)
auto &&range = for-range-initializer ;
auto begin = begin-expr ;
auto end = end-expr ;
for ( ; begin != end; ++begin ) {
for-range-declaration = * begin ;
statement
}
}
注意在语句中
auto begin = begin-expr ;
auto end = end-expr ;
begin
和 end
被复制。 Container::begin()
和 Container::end()
returns Iterator &
和 begin-expr 和 end-expr 是 lvalue-expressions,那么 Iterator
必须是 CopyConstructible。如果Container::begin()
和Container::end()
returnsIterator &&
和begin-expr和end-expr 是 xvalue-expressions,那么 Iterator
需要是 MoveConstructible。如果Container::begin()
和Container::end()
returnsIterator
和begin-expr和end-expr 是 prvalue-expressions,那么 Iterator
不需要 CopyConstructible 或 MoveConstructible 因为强制复制省略(C++17 起),但注意迭代器被认为是指针的抽象,它们通常应该是可复制的。
一个non-copyable类型不是迭代器,因为迭代器的概念要求类型满足CopyConstructible的概念。
也就是说,(除非我错过了),标准在技术上并不 range-based for 循环来实际使用迭代器。虽然您的示例确实尝试复制“迭代器”,因此无法工作,但只需稍作更改即可修复:
struct Container {
Iterator begin() { return {}; } // return by prvalue
Iterator end() { return {}; }
};
这在 C++17 之前不起作用,因为会从临时对象移动。因此,需要进行另一项更改,即使类型可移动。
In a C++ range-based for loop, can begin() return a reference to a non-copyable iterator?
没有,但它可以return这样的值non-iterator。
您总是可以创建一个 class 来存储您的引用,其复制构造函数只复制引用:
template<class T>
struct RangeForRef : private std::reference_wrapper<T> {
using std::reference_wrapper<T>::reference_wrapper;
bool operator!=(const RangeForRef& other) {
return this->get() != other.get();
}
void operator++() {
++(this->get());
}
decltype(auto) operator*() {
return *(this->get());
}
};
struct Container {
Iterator iterator;
RangeForRef<Iterator> begin() { return iterator; }
RangeForRef<Iterator> end() { return iterator; }
};
尽管我怀疑您的迭代器 class 应该首先保存对您的 non-copyable 内容的引用,例如:
struct Container {
NonCopyable data;
Iterator begin() {
// Iterator has a `NonCopyable*` member
// And copy assign / construct copies the pointer
return { data };
}
Iterator end() {
return { data };
}
};
更新:
感谢所有提交答案的人。
简而言之,答案是 begin()
和 end()
return 的“迭代器”必须是可复制的。
Artyer 提出了一个很好的解决方法:创建一个迭代器 class,其中包含对不可复制对象的引用(或者,指针)。下面是示例代码:
struct Element {};
struct Container {
Element element;
struct Iterator {
Container * c;
Iterator ( Container * c ) : c(c) {}
bool operator != ( const Iterator & end ) const { return c != end.c; }
void operator ++ () { c = nullptr; }
const Element & operator * () const { return c->element; }
};
Iterator begin () { return Iterator ( this ); }
Iterator end () { return Iterator ( nullptr ); }
};
#include <stdio.h>
int main () {
Container c;
printf ( "main %p\n", & c .element );
for ( const Element & e : c ) { printf ( "loop %p\n", & e ); }
return 0;
}
原题:
下面的 C++ 代码将无法编译(至少在 Ubuntu 20.04 上的 g++
版本 9.3.0 无法编译)。
错误信息是:
use of deleted function 'Iterator::Iterator(const Iterator&)'
根据错误,我得出 begin()
和 end()
编辑的“迭代器”return 必须可复制的结论是否正确?或者有什么方法可以使用 return 通过引用编辑的不可复制的迭代器?
struct Iterator {
Iterator () {}
// I want to prevent the copying of Iterators, so...
Iterator ( const Iterator & other ) = delete;
bool operator != ( const Iterator & other ) { return false; }
Iterator & operator ++ () { return * this; }
Iterator & operator * () { return * this; }
};
struct Container {
Iterator iterator;
Iterator & begin() { return iterator; }
Iterator & end() { return iterator; }
};
int main () {
Container container;
for ( const Iterator & iterator : container ) {}
// The above for loop causes the following compile time error:
// error: use of deleted function 'Iterator::Iterator(const Iterator&)'
return 0;
}
是的,迭代器必须是可复制的。这是因为范围迭代 is logically equivalent to the following code:
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
这是C++11版本。 C++17 和更高版本略有不同,但根本原因是相同的:__begin
和 __end
是 auto
,而不是 auto &
,或类似的东西.它们是 non-reference 类型。 “begin_expr”和“end_expr”,换句话说,就是最终调用自定义 begin()
和 [=] 的 begin
和 end
表达式18=].
即使您的 begin()
和 end()
returns 引用,它们也会被分配给 non-reference 类型,因此必须是可复制的。
请注意,即使情况并非如此,所示的实现也不是很有用,因为两个引用始终是同一个对象,因此开始和结束表达式最终将成为同一个对象,并且总是比较相等(希望如此)。
The range-based for statement
for ( init-statement(opt) for-range-declaration : for-range-initializer ) statement
is equivalent to
{ init-statement(opt) auto &&range = for-range-initializer ; auto begin = begin-expr ; auto end = end-expr ; for ( ; begin != end; ++begin ) { for-range-declaration = * begin ; statement } }
注意在语句中
auto begin = begin-expr ;
auto end = end-expr ;
begin
和 end
被复制。 Container::begin()
和 Container::end()
returns Iterator &
和 begin-expr 和 end-expr 是 lvalue-expressions,那么 Iterator
必须是 CopyConstructible。如果Container::begin()
和Container::end()
returnsIterator &&
和begin-expr和end-expr 是 xvalue-expressions,那么 Iterator
需要是 MoveConstructible。如果Container::begin()
和Container::end()
returnsIterator
和begin-expr和end-expr 是 prvalue-expressions,那么 Iterator
不需要 CopyConstructible 或 MoveConstructible 因为强制复制省略(C++17 起),但注意迭代器被认为是指针的抽象,它们通常应该是可复制的。
一个non-copyable类型不是迭代器,因为迭代器的概念要求类型满足CopyConstructible的概念。
也就是说,(除非我错过了),标准在技术上并不 range-based for 循环来实际使用迭代器。虽然您的示例确实尝试复制“迭代器”,因此无法工作,但只需稍作更改即可修复:
struct Container {
Iterator begin() { return {}; } // return by prvalue
Iterator end() { return {}; }
};
这在 C++17 之前不起作用,因为会从临时对象移动。因此,需要进行另一项更改,即使类型可移动。
In a C++ range-based for loop, can begin() return a reference to a non-copyable iterator?
没有,但它可以return这样的值non-iterator。
您总是可以创建一个 class 来存储您的引用,其复制构造函数只复制引用:
template<class T>
struct RangeForRef : private std::reference_wrapper<T> {
using std::reference_wrapper<T>::reference_wrapper;
bool operator!=(const RangeForRef& other) {
return this->get() != other.get();
}
void operator++() {
++(this->get());
}
decltype(auto) operator*() {
return *(this->get());
}
};
struct Container {
Iterator iterator;
RangeForRef<Iterator> begin() { return iterator; }
RangeForRef<Iterator> end() { return iterator; }
};
尽管我怀疑您的迭代器 class 应该首先保存对您的 non-copyable 内容的引用,例如:
struct Container {
NonCopyable data;
Iterator begin() {
// Iterator has a `NonCopyable*` member
// And copy assign / construct copies the pointer
return { data };
}
Iterator end() {
return { data };
}
};