Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can we reliably pre-increment/decrement rvalues?

For example, std::vector<int>::iterator it = --(myVec.end());. This works in GCC 4.4 but I have heard a rumor that it's not portable.

like image 405
ThomasMcLeod Avatar asked Nov 30 '12 18:11

ThomasMcLeod


3 Answers

It will only work if std::vector<int>::iterator is an object type with operator++ a member function. If it's a scalar type (e.g. int *), or operator++ is a non-member function, it will fail.

5.3.2 Increment and decrement [expr.pre.incr]

1 - The operand of prefix ++ is modified by adding 1 [...]. The operand shall be a modifiable lvalue. [...]
2 - [...] The requirements on the operand of prefix -- [...] are [...] the same as those of prefix ++. [...]

Non-const non-static member functions can be called on temporary objects (since they have non-const object type, per 9.3.2p3), but an lvalue reference parameter in a non-member function cannot bind to a temporary (13.3.3.1.4p3).

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

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

This means that it's nothing to do with the compiler, but rather the standard library; as you've observed libstdc++ is implemented with std::vector<int>::iterator an object type with member operator++, but your code could easily be compiled with the same compiler and a different standard library where std::vector<int>::iterator is int *, in which case it would fail.

std::vector, std::array and std::string are the only container templates that can sensibly be implemented with scalar (pointer) iterators, but that doesn't mean that calling ++ on other containers' iterators is safe; they could have non-member operator++ as T above.

To make an iterator to the before-the-end element, use std::prev:

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

std::prev and std::next are new in C++11, but are easily implementable in C++03.

like image 114
ecatmur Avatar answered Oct 06 '22 16:10

ecatmur


No that won't work in general.

In C++11 we have: auto it = std::prev(myVec.end());, which works reliably.

Boost has a similar function if you're in C++03, though it's trivial to write altogether:

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

Keep in mind you need at least one element in the range for this to make sense.


Here's an example of how your method won't work in general, consider this stripped-down std::vector<>:

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

Test program:

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

There is a corresponding std::next as well, implemented here:

template <typename BidirectionalIterator>
BidirectionalIterator
    next(BidirectionalIterator x,
         typename std::iterator_traits<BidirectionalIterator>::difference_type n = 1)
{
    std::advance(x, n);
    return x;
}
like image 23
GManNickG Avatar answered Oct 06 '22 14:10

GManNickG


This is indeed not portable, because there's no way to know whether myVec.end() returns an object of class type with operator -- overloaded by a member function or something else (maybe even a regular raw ponter). In the former case the overloaded -- will compile (operators overloaded by member functions can be applied to rvalues), while in the latter case it will not.

like image 3
AnT Avatar answered Oct 06 '22 15:10

AnT