在基于 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__endauto,而不是 auto &,或类似的东西.它们是 non-reference 类型。 “begin_expr”和“end_expr”,换句话说,就是最终调用自定义 begin() 和 [=] 的 beginend 表达式18=].

即使您的 begin()end() returns 引用,它们也会被分配给 non-reference 类型,因此必须是可复制的。

请注意,即使情况并非如此,所示的实现也不是很有用,因为两个引用始终是同一个对象,因此开始和结束表达式最终将成为同一个对象,并且总是比较相等(希望如此)。

[stmt.ranged]/1

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 ;

beginend 被复制。 Container::begin()Container::end() returns Iterator &begin-exprend-expr 是 lvalue-expressions,那么 Iterator 必须是 CopyConstructible。如果Container::begin()Container::end()returnsIterator &&begin-exprend-expr 是 xvalue-expressions,那么 Iterator 需要是 MoveConstructible。如果Container::begin()Container::end()returnsIteratorbegin-exprend-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 };
    }

};