【问题标题】:Why can't I decrement std::array::end()?为什么我不能减少 std::array::end()?
【发布时间】:2018-01-10 12:44:29
【问题描述】:

我正在为容器类型创建一个方便的display() 函数模板。最后一个元素的输出与其他元素不同,因此我检查 myIterator != --cont.cend(); 的时间。这适用于std::vector,但不适用于std::array。为什么?

这是一个 MWE(不是我的实际代码):

std::vector<double> vec({1,2});
std::array<double, 2> arr({{1,2}});
auto vecIt = --vec.end(); // OK
auto arrIt = --arr.end(); // error: lvalue required as decrement operand

【问题讨论】:

    标签: c++ iterator language-lawyer stdarray


    【解决方案1】:

    由于这是[expr.pre.increment][expr.post.increment] 都有以下限制:

    操作数应该是一个可修改的左值。

    现在,vec.end()arr.end() 都不是左值,但它们的两种类型都是实现定义的(对于 arrayvector)。在这两种情况下,一个简单的指针将满足这些容器的所有迭代器要求——这将是一种使用内置前缀和后缀增量的类型。在这种情况下,--c.end() 将由于引用的限制而格式错误。但是,如果迭代器类型是类类型,则上述限制不适用——因为我们没有使用内置的增量运算符——并且在类上调用operator--() 没有此限制在它上面(虽然它可以,如果成员函数是左值引用限定的)。

    因此,向量或数组的--c.end() 不能保证工作,因为如果end() 返回一个指针,这是格式错误的,并且允许end() 返回一个指针。在您的特定实现中,vectoriterator 具有类类型,但 arrayiterator 只是一个指针类型,这就是前者有效而后者无效的原因。

    首选std::prev(c.end()),它适用于所有实现的两种容器类型。

    【讨论】:

    • 基本上,迭代器要求不需要-- 处理任何迭代器右值。
    【解决方案2】:

    永远不要减少右值,即使它恰好编译。这对代码的读者来说是不直观的。

    请改用std::prev

    auto it = std::prev(arr.end());
    

    【讨论】:

      【解决方案3】:

      这取决于迭代器是如何定义的。

      似乎对于类模板std::array,迭代器被定义为一个指针。所以功能开始,结束。 cbegin, cend 只返回指针。因此,当指针按值返回时,您可能不会减少它,因为需要 lvalue..

      对于类模板std::vector,迭代器被定义为一个用户定义的类,为其定义了操作符--()。

      考虑以下演示程序

      #include <iostream>
      
      class Int
      {
      public:
          Int( int x = 0 ) : x ( x ) 
          {
      
          }
      
          Int & operator --()
          {
              --x;
              return *this;
          }
      
          friend std::ostream & operator <<( std::ostream &os, const Int &i )
          {
              return os << i.x;
          }
      private:
          int x;
      };
      
      int f( int x )
      {
          return x;
      }
      
      Int f( Int x )
      {
          return x;
      }
      
      int main() 
      {
          std::cout << --f( Int( 10 ) ) << std::endl;
      //  error: lvalue required as decrement operand
      //  std::cout << --f( 10 ) << std::endl;
      
          return 0;
      }
      

      考虑到你会写

      auto arrIt = std::prev( arr.end() );
      

      而不是

      auto arrIt = --arr.end();
      

      前提是您包含标题&lt;iterator&gt;

      您可以将运算符与类模板 std::array 的反向迭代器一起使用,因为标准明确定义了它们

      typedef std::reverse_iterator<iterator> reverse_iterator;
      typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
      

      而用户定义的类std::reverse_iterator 定义了operator --()

      这是一个演示程序

      #include <iostream>
      #include <array>
      
      int main() 
      {
          std::array<double, 2> arr = { { 1, 2 } };
      
          auto it = --arr.rend();
      
          std::cout << *it << std::endl;
      
          return 0;
      }
      

      【讨论】:

      • 这很有启发性。非常感谢!
      【解决方案4】:

      这是 C++ 标准的现实。

      如果Tstd::vectorstd::liststd::deque,则需要--T.end()。这在 C++11 之前是正确的,包括 C++11;之后有一些放松。

      【讨论】:

      • 呵呵,你有引用吗?
      • 有趣的是,列出 vector/list/deque 并具有该可选功能的表 68 已在 C++14(23.2.3 Sequence containers 中的表 101)中更改为 include @987654329 @也一样。看看 OP 是否会在 C++14 或更好的编译器上遇到同样的问题会很有趣。
      • 看起来最新的草案 N4713 已经解决了 DR355,消除了 --c.end() 需要编译的意外暗示。因此,对于更新的 C++ 版本,此答案不再正确。您仍然可以减少结束迭代器(对于双向序列),但如果它是右值则不一定。
      • @SebastianRedl:看起来我偶然发现了比我最初想象的更复杂的东西。 (当我正式学习 C++ 时,我的脑海中就已经有了它——早在 C++03 的时候。)如果人们想为答案做出贡献,我已经在维基上找到了。
      猜你喜欢
      • 1970-01-01
      • 2017-01-12
      • 2015-12-14
      • 1970-01-01
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-25
      相关资源
      最近更新 更多