【问题标题】:Can we reliably pre-increment/decrement rvalues?我们可以可靠地预先增加/减少右值吗?
【发布时间】:2012-11-19 00:48:24
【问题描述】:

例如,std::vector<int>::iterator it = --(myVec.end());。这在 GCC 4.4 中有效,但我听说它不是可移植的。

【问题讨论】:

  • 你有没有讨论它不便携的链接?

标签: c++ rvalue pre-increment


【解决方案1】:

仅当std::vector<int>::iterator 是具有operator++ 成员函数的对象类型时才有效。如果是标量类型(例如int *),或者operator++是非成员函数,则会失败。

5.3.2 递增和递减[expr.pre.incr]

1 - 前缀++ 的操作数通过添加1 [...] 进行修改。 操作数应该是一个可修改的左值。 [...]
2 - [...] 前缀-- [...] 的操作数要求[...] 与前缀++ 相同。 [...]

可以在临时对象上调用非常量非静态成员函数(因为它们具有非const 对象类型,根据 9.3.2p3),但非成员函数中的左值引用参数不能绑定到临时(13.3.3.1.4p3)。

struct S { S &operator++(); };
struct T { }; T &operator++(T &);
typedef int U;

++S();  // OK
++T();  // fails
++U();  // fails

这意味着它与编译器无关,而与标准库有关;正如您所观察到的,libstdc++ 是使用 std::vector<int>::iterator 实现的,该对象类型具有成员 operator++,但是您的代码可以很容易地使用相同的编译器和不同的标准库编译,其中 std::vector<int>::iteratorint *,在这种情况下它会失败。

std::vectorstd::arraystd::string 是唯一可以用标量(指针)迭代器实现的容器模板,但这并不意味着在其他容器的迭代器上调用 ++ 是安全的;他们可以有非会员 operator++ 和上面的 T

要对结束前元素创建迭代器,请使用std::prev

std::vector<int>::iterator it = std::prev(myVec.end());

std::prevstd::next 在 C++11 中是新的,但在 C++03 中很容易实现。

【讨论】:

  • 我检查了标准,没有看到标量右值不能预先递减的地方。你知道那在哪里吗?
  • @ThomasMcLeod:您可以轻松测试它++(1+1)
  • @ThomasMcLeod 它是 5.3.2p1;我在上面添加了一个报价。
【解决方案2】:

不,这通常不起作用。

在 C++11 中,我们有:auto it = std::prev(myVec.end());,它工作可靠。

如果你在 C++03 中,Boost 也有类似的功能,尽管写起来很简单:

template <typename BidirectionalIterator>
BidirectionalIterator
    prev(BidirectionalIterator x,
         typename std::iterator_traits<BidirectionalIterator>::difference_type n = 1)
{
    std::advance(x, -n);
    return x;
}

请记住,您需要在范围内至少有一个元素才能有意义。


这是一个示例,说明您的方法在一般情况下如何不起作用,请考虑这个精简的std::vector&lt;&gt;

#include <iterator>

namespace std_exposition
{
    template <typename T>
    struct vector
    {
        // this is compliant:
        typedef T* iterator;

        iterator end()
        {
            return std::end(data);
        }

        T data[4];
    };

    // manually implemented std::prev:
    template <typename BidirectionalIterator>
    BidirectionalIterator
        prev(BidirectionalIterator x,
             typename std::iterator_traits<BidirectionalIterator>::difference_type n = 1)
    {
        std::advance(x, -n);
        return x;
    }
}

测试程序:

int main()
{
    std_exposition::vector<int> myVec;

    // Won't compile (method in question):
    auto it0 = --(myVec.end());

    // Compiles
    auto it1 = std::prev(myVec.end());
    auto it2 = std_exposition::prev(myVec.end());
}

还有一个对应的std::next,在这里实现:

template <typename BidirectionalIterator>
BidirectionalIterator
    next(BidirectionalIterator x,
         typename std::iterator_traits<BidirectionalIterator>::difference_type n = 1)
{
    std::advance(x, n);
    return x;
}

【讨论】:

    【解决方案3】:

    这确实是不可移植的,因为没有办法知道myVec.end() 是否返回一个类类型的对象,其中运算符-- 被成员函数或其他东西重载(甚至可能是一个普通的原始指针)。在前一种情况下,重载的-- 将编译(成员函数重载的运算符可以应用于右值),而在后一种情况下则不会。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-12-04
      • 1970-01-01
      • 2015-06-26
      • 1970-01-01
      • 2022-08-19
      • 1970-01-01
      • 2012-09-30
      • 1970-01-01
      相关资源
      最近更新 更多