Minor Annoyances: Looping from 0 to size() using auto

deceptikon 5 Tallied Votes 307 Views Share

The annoying use case is this:

for (vector<int>::size_type i = 0; i < v.size(); i++) {
    cout << v[i] << ' ';

    if (i == v.size() - 1) {
        cout << endl;
    }
}

C++11 offers the auto keyword for initializer type deduction so that we can avoid verbose and ugly types like vector<int>::size_type. However, the naive attempt doesn't work:

for (auto i = 0; i < v.size(); i++) {
    cout << v[i] << ' ';

    if (i == v.size() - 1) {
        cout << endl;
    }
}

The reason it doesn't work is because 0 doesn't match the type of v.size(). Worse, v.size() is an unsigned type while the literal 0 is a signed integer. At most there will be a warning, but that's still annoying, so I set out to come up with a better option that's consistent across the board and has a more conventional syntax.

Option 1: decltype

C++11 also offers the decltype keyword for deducing the type of an expression or entity. In this case we can deduce the type of v.size() and use that for the type of i. Everything works nicely with perfect type matching and no warnings, but it's kind of ugly syntax-wise:

for (decltype(v.size()) i = 0; i < v.size(); i++) {
    cout << v[i] << ' ';

    if (i == v.size() - 1) {
        cout << endl;
    }
}

Option 2: Iterator arithmetic

An option that exists prior to C++11 is by using iterator subtraction to find the current index. It's effective and conventional in terms of loop syntax, but the arithmetic feels wrong to my eyes. Maybe I'm mentally compiling it into slower code than a more direct loop over an index:

for (auto x = v.begin(); x != v.end(); ++x) {
    cout << v[x - v.begin()] << ' ';

    if (x == v.end() - 1) {
        cout << endl;
    }
}

Also note that if you don't need the current index, the iterator can be used to acquire the value of the current item: cout << *x << ' ';. But if you don't need the current index, a range based for loop may be the superior option in the first place.

Option 3: ibegin() and iend()

This is the solution I came up with (see the snippet below for the implementation). The idea is to provide an index-based version of the begin() and end() non-member functions that return an iterator:

for (auto i = ibegin(v); i != iend(v); i++) {
    cout << v[i] << ' ';

    if (i == iend(v) - 1) {
        cout << endl;
    }
}

Notice how now the assumption is only that v is a container with an implementation of ibegin()/iend() and an overloaded subscript operator. Like with begin()/end(), this means v could be an array or a standard container, or even a custom container that provides suitable overloads.

In the case of the non-array overloads, the only assumption is that the container supports a member function called size() which returns a value of the index type. If that's not the case for user-defined container classes, ibegin() and iend() can be overloaded as necessary to meet the needs of the class (just like the standard begin() and end().

namespace ex {
    /*
        @description:
            Returns an index to the beginning of a subscriptable container.
    */
    template <typename Container>
    auto inline ibegin(Container&) -> decltype(Container().size())
    {
        return 0;
    }

    /*
        @description:
            Returns an index to the beginning of a subscriptable container.
    */
    template <typename Container>
    auto inline ibegin(const Container&) -> decltype(Container().size())
    {
        return 0;
    }

    /*
        @description:
            Returns an index to the end of a subscriptable container.
    */
    template <typename Container>
    auto inline iend(Container& c) -> decltype(c.size())
    {
        return c.size();
    }

    /*
        @description:
            Returns an index to the end of a subscriptable container.
    */
    template <typename Container>
    auto inline iend(const Container& c) -> decltype(c.size())
    {
        return c.size();
    }

    /*
        @description:
            Returns an index to the beginning of an array.
    */
    template<class T, size_t N>
    inline auto ibegin(T (&)[N]) -> decltype(N)
    {
        return 0;
    }

    /*
        @description:
            Returns an index to the end of an array.
    */
    template<class T, size_t N>
    inline auto iend(T (&)[N]) -> decltype(N)
    {
        return N;
    }
}