Can we reliably pre-increment/decrement rvalues?
Asked Answered
H

3

6

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.

Houghton answered 30/11, 2012 at 18:53 Comment(1)
Do you have a link where it discusses it not being portable?Hatty
E
8

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.

Eastbound answered 30/11, 2012 at 18:59 Comment(3)
I checked the standard and didn't see where a scalar rvalue cannot be pre-decremented. Do you happen to know where that is?Houghton
@ThomasMcLeod: you can test it out easily ++(1+1).Jug
@Houghton it's 5.3.2p1; I've added a quote above.Eastbound
G
6

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;
}
Geller answered 30/11, 2012 at 19:3 Comment(0)
H
3

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.

Hauge answered 30/11, 2012 at 19:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.