[英]In a C++ range-based for loop, can begin() return a reference to a non-copyable iterator?
更新:
感謝所有提交答案的人。
簡而言之,答案是begin()
和end()
返回的“迭代器”必須是可復制的。
Artyer 提出了一個很好的解決方法:創建一個包含對不可復制對象的引用(或者,指針)的迭代器類。 下面是示例代碼:
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()
返回的“迭代器”必須是可復制的嗎? 或者有什么方法可以使用通過引用返回的不可復制的迭代器?
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;
}
是的,迭代器必須是可復制的。 這是因為范圍迭代在邏輯上等同於以下代碼:
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 &
或類似的東西。 它們是非引用類型。 “begin_expr”和“end_expr”,用很多的話來說,是begin
和end
表達式,最終調用您的自定義begin()
和end()
。
即使您的begin()
和end()
返回引用,它們也會被分配給非引用類型,因此必須是可復制的。
請注意,即使情況並非如此,所顯示的實現也不是很有用,因為兩個引用始終是同一個對象,因此開始和結束表達式最終將是同一個對象,並且總是比較相等(希望)。
基於范圍的 for 語句
for ( init-statement(opt) for-range-declaration : for-range-initializer ) statement
相當於
{ 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()
返回Iterator &
並且begin-expr和end-expr都是左值表達式,那么Iterator
必須是可復制構造的。 如果Container::begin()
和Container::end()
返回Iterator &&
並且begin-expr和end-expr是 xvalue-expressions,則Iterator
需要是 MoveConstructible。 如果Container::begin()
和Container::end()
返回Iterator
並且begin-expr和end-expr是純右值表達式,則Iterator
不需要是 CopyConstructible 或 MoveConstructible 因為強制復制省略(因為 C++ 17),但請注意,迭代器被視為指針的抽象,它們通常應該是可復制的。
不可復制的類型不是迭代器,因為迭代器的概念要求類型滿足 CopyConstructible 的概念。
也就是說,(除非我錯過了),標准在技術上並不是基於范圍的 for 循環來實際使用迭代器。 雖然您的示例確實嘗試復制“迭代器”,因此無法正常工作,但只需稍作更改即可修復:
struct Container {
Iterator begin() { return {}; } // return by prvalue
Iterator end() { return {}; }
};
這在 C++17 之前不起作用,因為會從臨時對象移動。 因此,需要進行另一項更改,使類型可移動。
在基於 C++ 范圍的 for 循環中,begin() 可以返回對不可復制迭代器的引用嗎?
不,但它可以向這樣的非迭代器返回一個值。
你總是可以創建一個類來存儲你的引用,它的復制構造函數只是復制引用:
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; }
};
盡管我懷疑您的迭代器類應該首先保留對不可復制內容的引用,例如:
struct Container {
NonCopyable data;
Iterator begin() {
// Iterator has a `NonCopyable*` member
// And copy assign / construct copies the pointer
return { data };
}
Iterator end() {
return { data };
}
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.