Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

For loop index type deduction best practice

Let's say, I have a container c of a type that provides a size() method and I want to loop over this container while keeping track of each item's index:

for (/*TODO*/ i = 0; i < c.size(); i++) {...}

In a post-C++11 world, where automatic type deduction solves so many problems nicely. What should we use in place of the TODO above? The only thing that seems correct to me, no matter what the type of size() is, is the following:

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

But this seems overly verbose and ,in my opinion, doesn't help readability.

Another solution might be this:

for (auto end = c.size(), i = 0; i < end; i++) {...}

But this doesn't help readability either and, of course, doesn't have the same semantics as the original snippet.

So, my question is: what is the best way to deduce the type of a loop index variable, given only the type of the index' limit.

like image 837
mtvec Avatar asked Oct 01 '14 06:10

mtvec


3 Answers

Short answer to the first question in your text: You should replace the /*TODO*/ by unsigned, std::size_t or something similar, meaning: don't bother deducing the type, just pick a type suitable for any reasonable container size.

This would be an unsigned, reasonably large type so the compiler is not tempted to yell at you beacuse of possible precision losses. In the comments above you write that size_t is not guaranteed to be a good replacement to decltype(c.size()), but while it is not impossible to implement a container that has an index incompatible to size_t, such indizes would most surely not be numbers (and thus incompatible to i = 0), and the containers would not have a size method either. A size() method implies a nonnegative integral, and since size_t is designed for exact those numbers, it will be close to impossible to have a container of a size that cannot be represented by it.

Your second question aims at how to deduce the type, and you already have provided the easiest, yet imperfect answers. If you want a solution that is not as verbose as decltype and not as surprising to read as auto end, you could define a template alias and a generator function for the starting index in some utility header:

template <class T> 
using index_t = decltype(std::declval<T>().size());

template <class T, class U>
constexpr index_t<T> index(T&&, U u) { return u; }

//and then in the actual location of the loop:
for (auto i = index(c,0); i < c.size(); ++i) {...}
//which is the same as
for (auto i = index_t<std::vector<int>>(0); i < c.size(); ++i) {...}

If you want to have a more general index-type, e.g. for arrays and classes that don't have a size method, it gets a bit more complicated, because template aliases may not be specialized:

template <class T>
struct index_type {
  using type = decltype(std::declval<T>().size());
};

template <class T>
using index_t = typename index_type<T>::type;

template <class T, class U>
constexpr index_t<T> index(T&&, U u) { return u; }

//index_type specializations
template <class U, std::size_t N>
struct index_type<U[N]> { 
  using type = decltype(N); 
};

template <>
struct index_type<System::AnsiString::AnsiString> { //YUCK! VCL!
  using type = int; 
};

However, this is a lot of stuff just for the few cases where you actually need an index and a simple foreach loop is not sufficient.

like image 115
Arne Mertz Avatar answered Nov 10 '22 16:11

Arne Mertz


If c is a container you can use container::size_type.

like image 2
TNA Avatar answered Nov 10 '22 16:11

TNA


Here is the precedence that I follow

1) range-for
2) iterator/begin()/end() with type deduced with auto.

For cases where indexing is required, which is the subject here, I prefer to use

for( auto i = 0u; i < c.size(); ++i) {...}

Even if I misses to add u in 0, compiler will warn me anyway.


Would have loved decltype if it is not too verbose

for (decltype(c.size()) i = 0; i < c.size(); i++) {...}
like image 1
dlmeetei Avatar answered Nov 10 '22 16:11

dlmeetei