在基于C ++范围的for循环中,begin是否可以返回对不可复制迭代器的引用?

问题描述

更新

感谢所有提交答案的人。

简而言之,答案是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;
}

解决方法

是的,迭代器必须是可复制的。这是因为范围迭代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 &或类似的东西。它们是非引用类型。用很多词来说,“ begin_expr”和“ end_expr”是beginend表达式,它们最终调用您的自定义begin()end()

即使您的begin()end()返回引用,它们也会被分配为非引用类型,因此必须是可复制的。

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

,

[stmt.ranged]/1

基于范围的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 ;

beginend被复制。 Container::begin()Container::end()均返回Iterator & begin-expr end-expr 均为左值表达式,然后{{1} }必须是CopyConstructible。如果IteratorContainer::begin()返回Container::end(),并且 begin-expr end-expr 是xvalue表达式,则{{1} }必须是MoveConstructible。如果Iterator &&Iterator返回Container::begin(),并且 begin-expr end-expr 是prvalue表达式,则{{1} }由于强制性的复制省略(自C ++ 17开始),因此不必是CopyConstructible或MoveConstructible的,但是请注意,迭代器被视为指针的抽象,它们通常应该是可复制的。

,

不可复制类型不是Iterator,因为Iterator的概念要求该类型满足CopyConstructible的概念。

也就是说,(除非我没有错过它),标准在技术上并不是基于范围的for循环实际使用Iterators。虽然您的示例确实尝试复制“迭代器”,因此无法正常工作,但进行较小的更改就可以解决:

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 };
    }

};

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...