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.
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.
If c is a container you can use container::size_type
.
Here is the precedence that I follow
1)
range-for
2)iterator/begin()/end()
with type deduced withauto
.
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++) {...}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With