For loop index type deduction best practice

主宰稳场 提交于 2020-03-17 08:42:47

问题


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.


回答1:


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.




回答2:


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




回答3:


Hmm... this needs C++14 or a compiler that supports auto in the lambda parameters. If you're using this pattern a lot, then a helping function might be useful:

template< typename Container, typename Callable >
void for_each_index( Container& container, Callable callable )
{
    for (decltype(container.size()) i = 0; i < container.size(); i++)
    {
        callable(i);
    }
}

Use as:

for_each_index(c, [] (auto index) {
    // ...
});



回答4:


As a matter of fact, I have seen plenty of times (cough llvm, clang) where they do use

for (/* type */ iter = begin(), End = end(); iter != End; ++i);

The advantage of having End evaluated at the beginning is that the compiler can be sure that it doesn't need to call it every time. For collections that where calculating the end is trivial and the compiler is already able to deduce that it doesn't need to call end() multiple times it won't help, but in other cases it will.

Or you could always use a helper:

Implementing enumerate_foreach based on Boost foreach




回答5:


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++) {...}


来源:https://stackoverflow.com/questions/26135284/for-loop-index-type-deduction-best-practice

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!