Reader Q&A: auto and for loop index variables

[Edit: I really like the ‘range of values’ several commenters proposed. We do need something like that in the standard library, and it may well come in with ranges, but as you can see there are several simple ways to roll your own in the meantime, and some third-party libraries have similar features already.]

Today a reader asked the following question:

So I’ve been reading all I can about c++11/c++14 and beyond when time permits.  I like auto, I really do, I believe in it.  I have a small problem I’m trying to decide what to do about.  So in old legacy code we have things like this:

for (int i = 0; i < someObject.size(); i++) { … }

For some object types size might be unsigned, size_t, int, int64_t etc…

Is there a proper way to handle this generically with auto?  The best I could come up with is:

auto mySize = someObject.size();

for (auto i = decltype(mySize){0}; i < mySize; i++) { … }

But I feel dirty for doing it because although it actually is very concise, it’s not newbie friendly to my surrounding coworkers who aren’t as motivated to be on the bleeding edge.

Good question.

First, of course, I’m sure you know the best choice is to use range-for where that’s natural, or failing that consider iterators and begin()/end() where auto naturally gets the iterator types right. Having said that, sometimes you do need an index variable (including for performance) so I’ll assume you’re in that case.

So here’s my off-the-cuff answer:

  • If this isn’t in a template, then I think for( auto i = 0; etc. is fine, and if you get a warning about signed/unsigned mismatch just write for(auto i = 0u; etc. This is all I’ve usually needed to do.
  • If this is truly generic code in a template, then I suppose for( auto i = 0*mySize; etc. isn’t too bad – it gets the type and it’s not terribly ugly. Disclaimer: I’ve never written this, personally, as I haven’t had a need (yet). And I definitely don’t know that I like it… just throwing it out as an idea.

But that’s an off-the-cuff answer. Dear readers, if you know other/better answers please tell us in the comments.

52 thoughts on “Reader Q&A: auto and for loop index variables

  1. Adam, even better than both in that it doesn’t introduce variables into the outer scope:

    for (decltype(object.size()) i = 0, n = object.size(); i < n; ++i)
    

    That is, unless the loop body does something that can change the size of object, in which case you do need your second form, and you have to live with any extra size() calls.

  2. Hi all, I’m not sure about insert object.size() (or any other function) in for statement is good solution, i think the

    auto object_size = object.size();
    decltype(object_size) i = 0;
    for (i = 0; i < object_size; i++) { … }

    it’s better than

    for (decltype(object.size()) i = 0; i < object.size(); i++) { … } 

    because we don’t call object.size() function in every iteration (in second example, it’s called twice)

  3. I want the nifty way python does it

    for (tie(i, elem) : enumerate(list)) {
       ...
    }
    

    Just need to be able to define variables in tie.

  4. I would actually like to propose a breaking change; I think

    auto i = 0;
    

    should not compile. The reason is, 0 is a valid constant for all signed integral types, all unsigned integral types, all floating-point types and all pointer types. So the type really can’t be deduced. This could lead to code like

    auto i=0, sz=77u;
    

    working, since i don’t participate in deduction, and the rest agree

  5. I am going to go against the tide here and say that I *don’t* want i to become size_t.

    For a counting for loop, especially one that goes from 0, there is never any reason to use an unsigned value, and since I try to use signed integers in most other places, this can lead to having to cast the index back to signed inside the loop.

    So a nice benefit of creating an iterator factory function to use in a for-each loop is that it can actually convert the argument to a signed 32 or 64 bit integer (and for correctness sake throw an exception if the argument is too large to fit).

  6. A lot of the answers here seem to me to be needlessly complex.
    If our concern is readability and newbie-friendliness, then all you need to do is set the tricky-ish definition on its own, so it’s clear:

    	auto mySize = someObject.size();
    	decltype(mySize) i; // index looping from 0 to mySize
    	for (i=0; i < mySize; i++) { … }
    

    I don’t see this as hurting our pro-auto-ness; decltype isn’t any less automatic or flexible than auto is. Our type is *still* deduced at compile-time according to the values it depends on, rather than being hard-coded. But you could also use the explicitly type initializer idiom:

    	auto mySize = someObject.size();
    	auto i = static_cast<decltype(mySize)>(0); // index looping from 0 to mySize
    	for (i=0; i < mySize; i++) { … }
    

    I this is maybe a little less readable for novices, but the point is that the only conceivably-confusing part is on its own, doing *only* the job of declaration. That makes it easy to understand – you don’t need to understand the whole loop; and the loop (in and of itself) is still perfectly clear. All that’s happening is that the c++11-newbie says “Huh, a variable is being declared here and I don’t really understand its type or why it’s so complicated”; and then they look it up and have a clear, obvious answer.

    (This is also why I left “i=0” in, even though I could rely on initialization at declaration – I want the loop to be crystal-clear, even if the type of the index variable isn’t.)

  7. That works. You could also multiply with 0:
    for(auto mySize = someObject.size(), i = mySize * 0; i < mySize; i++) …

  8. I wonder whether this is acceptable?

    for(auto mySize = someObject.size(), i = mySize – mySize; i < mySize; i++) …

  9. Thanks, for catching that Joe. The above versions don’t work indeed (segfaults.) But the following works:

    for (auto i = someObject.size(); i > 0; --i) {
      // use i-1 as the index
    }
    // Or the bit more cryptic:
    for (auto i = someObject.size(); i-- > 0; ) {
      // Use i
    }
    
  10. p sarkar, your code will result in an endless loop, at least for standard containers like vectors and strings. std::size_t is unsigned, so it is never less than 0.

  11. Would love range based solutions. If order of evaluation does not matter, one way to side-step the issue is:

    for (auto i = someObject.size() - 1; i >= 0; --i) { ... }
    for (auto i = someObject.size(); --i >= 0; ) { ... }
    
  12. Somehow my link didn’t make it into my reply, another shot:

  13. I use such code for the primary reason that we compile with -Wall, and signed/unsigned comparisons won’t let me get away with auto i = 0 :-)
    Besides, it looks much more attractive, and compiles into optimal code just like hand-written loop.

  14. Ranges is a good idea in general, but they wouldn’t resolve this issue, they would just provide a superficially different syntax.
    The issue here is the type conversion, and it’s int 0 that should be converted to the size_type, not the other argument to int. And unless we decide on some construct that always casts the first argument to the second argument type (might not be a great idea) we have to specify the type explicitly. Thus, the obvious solutions are still the most readable and correct ones:

    for(size_type i = 0, size = someObject.size(); i < size; ++i)  ...
    for(auto i = (size_type)0, size = someObject.size(); i < size; ++i)  ...
    

    And if size_type is something frequently found in the code, then creating a user-defined literal will make it shorter, and easier to read:

    size_type operator"" _sz(unsigned long long i) { return size_type(i); }
    for(auto i = 0_sz, size = someObject.size(); i < size; ++i)  ...
    

    Also, if 0 is frequently converted to size_type, it makes sense to define a constant of that type:

    const size_type zero = (size_type)0;
    for(auto i = zero, size = someObject.size(); i < size; ++i)  ...
    
  15. How about

        std::vector< int > v { 0, 1, 2, 3, 4 };
    
        for ( auto&& t = v.size(), i = 0*t; i < t; ++i )
        {
            std::cout << i << "\n";
        }
    
  16. One thing worth noting also, is that the for-range-loop construct does not require real iterators (ie: it does not check any iterator traits, and only use a very small part of the public signature of a full-fledged iterator).
    For a type to work with the for range loop, you only need to have a

    begin(MyType&)

    and

    end(MyType&)

    returning an object supporting the following members:

    T operator*()const; // return the value currently pointed to by the iterator
    void operator++(); // move the iterator to the next value
    bool operator!=(const TIterator&) const; // compare the iterator to another one (used to check if we reached the end of the loop
    

    So it is really easy to extend existing components to support a for-range-loop construct.

  17. It might be overkill, but one option is to define an inline function that returns a 0 of the correct type for the container.

    template <class C>
    auto constexpr zero(const C &c) -> decltype(size(c)) {
        return 0;
    }
    

    Then user code could look like

    for (auto i = zero(myContainer); i < size(myContainer); ++i) {
       /// Do whatever
    }
    

    An possible alternative name for this function is firstIndex.

  18. I usually just do this:

    for(int i = 0; i < int(container.size()); ++i) {
    }
    

    It’s not perfect, but the majority of the time my array will never have anything close to 2 billion elements. Having a habit of using signed ints also prevents writing infinite loops when you traverse the array from back to front using indices. Using ssize_t could be another option if you’re concerned about sizeof(int).

    I like to use ints everywhere unless a more specific integral type is needed. If you make your loop variable unsigned then when you use it within the loop you’re likely to be doing math and comparisons between signed and unsigned.

    Fancy ranges and meta programming are nice but a lot of times we just have 2 arrays and want to iterate over them both with a simple index. No reason to over think things.

  19. Wow some really scary answers! If not a generic template, then leave as is, if a template then rewrite using the appropriate iteration style. Life can be simple sometimes…..even in c++!

  20. Although this isn’t much different than other suggestions, I would suggest something like:

    
      for (auto i : indices_of(someObject)) { ... }
    
    

    where `indices_of` is a function which returns a suitable range object. For example:

    
    template <typename T>
    struct index_range {
      T n;
    
      struct iterator {
        T index;
    
        bool operator!=(iterator that) const { return index!=that.index; }
        iterator operator++() { ++index; return *this; }
        T operator*() const { return index; }
      };
    
      iterator begin() const { return {0}; }
      iterator end() const { return {n}; }
    };
    
    template <typename T, typename Index = typename T::size_type>
    index_range<Index> indices_of(const T& container)
    {
      return {container.size()};
    }
    
    int main()
    {
      std::vector<int> v = {1,2,3};
    
      for (auto i : indices_of(v)) {
        std::cout << i << "\n";
      }
    }
    
    

    One nice thing about this approach is that `indices_of` could even be generalized to work with `std::map` or other containers whose indices aren’t integers, or whose indices don’t start at zero.

  21. I have seen many suggestions for ranges and I like them. However, I would like to be able to quickly construct ranges for certain data structures:

    for (auto i : someContainer.range()){    ... use i as an index ...}
    

    I personally do not know if this is feasable, but I just look how the API would be the cleanest. In general, this .range() function should only be available for data structures with simple integer domain for keys.

  22. Sorry, one function was missing:

    template <typename T>
    static inline constexpr auto null(T&&) noexcept(noexcept(null<T>())) {
        return null<T>();
    }
    
  23. I also like the ‘range of values’ approach because it enforces code locality as the iteration boundaries are tied together and it is also very succinct.
    As for the “null of a certain index type” problem we have some handy functions in our code base that merely call the default constructor (if any) and is basically syntactic sugar for the decltype(mySize){} approach, but clearly states the intent of the caller:

    template <typename T>
    using remove_cv_ref_t = std::remove_const_t<std::remove_reference_t<T>>;
    
    template <typename T>
    using has_nothrow_default = std::is_nothrow_default_constructible<remove_cv_ref_t<T>>;
    
    template <typename T>
    static inline constexpr remove_cv_ref_t<T>  null() noexcept(has_nothrow_default<T>{}()) {
        static_assert(std::is_default_constructible<remove_cv_ref_t<T>>{}(),
                      "type must be default constructible to generate null object!");
        return {};
    }
    

    to be used like this:

    constexpr std::size_t sizeBuffer = 64;
    for (auto i = null(sizeBuffer); i < sizeBuffer; ++i) {
        static_assert(std::is_same<decltype(i), std::size_t>::value, "???");
        // ...
    }
    
    constexpr std::uint8_t numElems = 64;
    for (auto i = null(numElems); i < numElems; ++i) {
        static_assert(std::is_same<decltype(i), std::uint8_t>::value, "???");
        // ...
    }
    
  24. To be honest, I just use

    for (size_t i = 0; i < v.size(); i++)

    We can talk all we want about ranges. Sure, they would be nice, but we don’t have them yet, and especially for newbies, are we going to recommend a 3rd party library or writing our own solution for something so trivial? Just use size_t. It’s what the standard library uses, and so good practice IMO should be to use size_t (or a lesser unsigned type) for the size for your own classes.

    It work seamlessly with the standard library and it should work with most 3rd party libraries and/or your own classes without signed/unsigned warnings and/or narrowing warnings.

  25. It’s actually a very interesting topic. I see a lot of unsigned/signed mismatches from static analysis tools due to these situations.

    I currently favour:

    for (decltype(someObject.size()) i = 0; i < someObject.size(); i++) { … }

    I hope in the near future to be able to use non-member std::size() which is more generic as it works with built-in arrays too:

    for (decltype(std::size(someObject)) i = 0; i < std::size(someObject); i++) { … }
  26. The fact that some programmers may not be familiar with a language feature is not a reason to avoid it, particularly if it makes the code more correct! decltype is fine here, and you can write it even more neatly than the original:

    for (decltype(someObject.size()) i = 0; i < someObject.size(); i++) { … }

    All these answers suggesting ranges are great, but even farther afield and less searchable than decltype.

  27. In N4254 I proposed the “z” suffix for size_t literals:

    auto s = 0z; // s has type size_t

    This allows code like this:

    #include <cstddef>
    #include <vector>
    using namespace std::support_literals;
    
    int main()
    {
      auto v = std::vector<int> { 98, 03, 11, 14, 17 };
      for (auto i = 0z, s = v.size(); i < s; ++i) { 
        /* use both i and v[i] */ 
      }
    }
    
  28. I wrote a interval arithmetic library (https://github.com/elliotgoodrich/EZInterval) that allows iterating over intervals. The ez::make_interval variable has overloaded operator[] and operator() to let you choose whether the interval is open or closed.

    // prints 012345
    for(auto i : ez::make_interval[0][5])
        std::cout << i;
    
    // prints 01234 (
    for(auto i : ez::make_interval[0](5))
        std::cout << i;
    
    // prints 12345
    for(auto i : ez::make_interval(0)[5])
        std::cout << i;
    
    // prints 1234
    for(auto i : ez::make_interval(0)(5))
        std::cout << i;

    This works with any other type that acts like a numeric type, e,g pointers and iterators

    std::vector<int> v = {1, 11, 21, 1112, 3112, 132112};
    
    // prints "1 11 21 1112 3112 132112 "
    for(std::vector<int>::iterator it : ez::make_interval[v.begin()](v.end()))
        std::cout << *it << " ";

    I’ve made a change and will commit later so that the type of i in the examples above will be the std::common_type of the lower and upper bound variables so ez::make_interval[0](size) will give you back a type of std::size_t when you iterate over it.

  29. (please excuse the spam, there’s no preview for comments…)

    The 0*-trick only works if decltype(std::declval<int>() * std::declval<decltype(someObject.size())>()) is the same as decltype(someObject.size()).

    This is the case for int, uint, size_t and ssize_t, but not for “smaller” types, such as short or uchar, because they’re promoted to int for any arithmetic. That also rules out xor and subtracting the size form itself to create a 0 value.

    In generic code, what’s wrong with plain old (for a certain definition of “old”)

    for ( auto end = someObject.size(), i = decltype(end)(0); i != end; ++i)
    

    ?

  30. similar to Herb’s solution, but without a multiplication in sight: for( auto i = mySize – mySize; …

  31. As it was mentioned earlier: writing irange(end), irange(begin, end) and irange(begin, end, step) range wrappers (returning iterators) is simple to write yourself and from what I’ve checked even Visual Studio (which many times failed to optimize enough) and the performance (and assembly for anyone wondering) is the same as for regular for but you get terse syntax.

  32. Guys, what about performance of

    for (auto i : ranges::view::iota(0, size-1))
    

    compared to the simpler:

    for (auto i=0; i<size; i++)
    

    ?

    Is the compiler still able to optimize/vectorize? I’m asking because I don’t know and I’m sure some of you do.
    Thanks!

    Marco

  33. Here’s an idea off the top of my head:

    template < typename C >
    auto begin_index( const C &c ) -> decltype( c.size() )
    {
    	return 0;
    }
    
    template < typename T >
    void f( const std::vector< T > &v )
    {
    	for ( auto i = begin_index( v ); i < v.size(); ++i ) { ... }
    
    	// if C++17 brings us std::size(), then this will look more elegant:
    	for ( auto i = begin_index( v ); i < size( v ); ++i ) { ... }
    }
    
  34. How about something like this? It’s a back-of-the-envelope solution, and I haven’t given more than a few seconds to the naming of it all, but it works for the obvious cases (I have not done anything that remotely looks like real testing, and it’s a bit late here):

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <iterator>
    using namespace std;
    
    template <class C>
       auto size(const C &cont) -> decltype(cont.size())
       {
          return cont.size();
       }
    template <class C, std::size_t N>
       std::size_t size(const C (&arr)[N])
       {
          return N;
       }
    
    template <class T>
       class value_range
          : public std::iterator<std::bidirectional_iterator_tag, T>
       {
          T first, last;
       public:
          template <class U>
             class value_iterator
             {
                U cur;
             public:
                value_iterator(U init)
                   : cur{init}
                {
                }
                value_iterator& operator++()
                {
                   ++cur;
                   return *this;
                }
                value_iterator operator++(int)
                {
                   auto temp { *this };
                   operator++();
                   return temp;
                }
                value_iterator& operator--()
                {
                   --cur;
                   return *this;
                }
                value_iterator operator--(int)
                {
                   auto temp{ *this };
                   operator--();
                   return temp;
                }
                bool operator==(const value_iterator &other) const
                {
                   return cur == other.cur;
                }
                bool operator!=(const value_iterator &other) const
                {
                   return !(*this == other);
                }
                U operator*()
                {
                   return cur;
                }
                U operator*() const
                {
                   return cur;
                }
             };
          using iterator = value_iterator<T>;
          using const_iterator = value_iterator<T>;
          value_range(T first, T last)
             : first{ first }, last{ last }
          {
          }
          iterator begin() { return first; }
          iterator end() { return last; }
          const_iterator begin() const { return first; }
          const_iterator end() const { return last; }
          const_iterator cbegin() const { return first; }
          const_iterator cend() const { return last; }
       };
    
    template <class C>
       auto value_range_from(C &cont) -> value_range<decltype(size(cont))>
       {
          return value_range<decltype(size(cont))>{ 0, size(cont) };
       }
    
    int main()
    {
       int arr[] = { 2, 3, 5, 7, 11 };
       vector<int> v(begin(arr), end(arr));
       for (const auto &val : arr)
          cout << val << ' ';
       cout << '\n';
       for (const auto &val : v)
          cout << val << ' ';
       cout << '\n';
       for (auto i : value_range_from(arr))
          cout << "arr[" << i << "] == " << arr[i] << "; ";
       cout << '\n';
       for (auto i : value_range_from(v))
          cout << "v[" << i << "] == " << v[i] << "; ";
       cout << '\n';
    }
    
    
  35. @Juan Carlos Arevalo Baeza

    Using range-v3 (and a range compatible range-for-loop) your idea looks like:

    for (auto i : ranges::view::iota(0, size-1))

    I guess we can also get indexes and values together:

    for (auto i_v : ranges::view::zip(ranges::view::iota(0), v))
    std::cout << "Index: " << std::get(*i) << " Value: " << std::get(*i) << '\n';

  36. ‘Generic’ code is good not just for templates, but also for portable code, where different platforms may define things differently. So in non-template code you might need `0` to avoid warnings on one platform and `0u` to avoid them on another.

    Needing indexes and values seems common enough that perhaps their should be a special syntax for it.

    Failing that perhaps a standard algorithm: `for_each(Range &&r, Functor &&f)` where the functor can optional accept both an index and the elements.

    And one maybe-improvement over the options presented here: `for (auto size = v.size(), i = 0*size; i < size; ++i) {`

  37.     for ( auto i: count_until(mySize) ) ...
    

    Is indeed the way to go; aside from that though, we still need a (preferably core-language) integer-literal for std::size_t (also for the std::[u]int*_t-typedefs). It is really very sad that there is no simpler way of generating a zero of those types than writing out something like std::size_t{}.

    std::size_t is certainly one of the most often needed integer-types, but people still access containers with ints because they are so much more convenient to type. This is really a terrible situation.

  38. Great points on the index-range examples. We do need an iterable ‘range of values’ in the standard library, but you can roll your own in the several ways suggested in the meantime.

  39. @Herb, I was wondering if you agree with these statements:

    1) “Using an unsigned instead of an int to gain one more bit to represent positive integers is almost never a good idea.” (Stroustrup)

    2) Implicit conversion rules make unsigned types into bug attractors (http://stackoverflow.com/a/10168569/297451)
    Eg: size_t x = 0; for(size_t i=10; i>=x; –i) {}

    If so, is it reasonable to cast away the unsigned-ness with a static_cast or boost::numeric_cast?

  40. I think a simple counting range would get the best of the two worlds:

    for ( auto i: count_until(mySize) ) ...

    (assuming the compiler can optimize this as efficiently as plain for loop)

    Implementation of such a range is trivial and left as an exercise for a reader :)

  41. How about:

      auto mySize = someObject.size(), i = mySize;
      for (i = 0; i < mySize; i++) { }
    
  42. I believe range-for is still the solution here. I’ll use boost::irange here, but you can roll your own simple wrapper for this quite easily:

    #include <boost/range/irange.hpp>
    // ...
    template<typename T, typename U, typename V = int>
    inline decltype(auto) range(T a, U b, V s = 1) {
      using D = decltype(true ? a : b); 
      return boost::irange<D>(a, b, s);
    }
    // ...
    void g(...);
    // ... 
    auto f(std::vector<int> const& v) {
      for(auto&& i : range(0, v.size())) {
        g(i);
      }
    }
    

    This lets the usual integer type promotion machinery of C++ do its thing (which, depending how you see it, can be a good or bad thing—but it’s predictable at least).

  43. The obvious ideal (IMHO) is to allow range-for with ranges of values instead of iterators. I don’t know if the ranges proposals or discussions are contemplating this. So for instance, this would be something like:

    for (auto i : range_from_to{0, mySize-1})
    {
        ... use i as an index ...
    }
    

    Names are, of course, up for grabs. This is implementable today. range_from_to “just” needs to return something that resembles input iterators (that implements the input iterator interface) when begin() and end() are called. The devil being potentially in the details (in the implementation of the end() sentinels and comparison operators, really).

    With this in the standard library, classic for-loops should be relegated to one-time situations where we don’t have a range readily available, as it’d be more cumbersome to implement the range than to just use the raw loop.

    JCAB

Comments are closed.