Для наилучшей практики вычитания типа индекса индекса цикла

Скажем, у меня есть контейнер c типа, который предоставляет метод size(), и я хочу перебрать этот контейнер при отслеживании каждого индекса элемента:

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

В пост-С++ 11 мире, где автоматический вывод типа решает так много проблем. Что мы должны использовать вместо TODO выше? Единственное, что кажется мне правильным, независимо от типа size(), заключается в следующем:

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

Но это кажется слишком многословным и, на мой взгляд, не помогает читабельности.

Другим решением может быть следующее:

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

Но это также не помогает читабельности и, конечно же, не имеет той же семантики, что и исходный фрагмент.

Итак, мой вопрос: какой лучший способ вывести тип индексной переменной цикла, учитывая только тип ограничения индекса.

Ответ 1

Короткий ответ на первый вопрос в тексте: вы должны заменить /*TODO*/ на unsigned, std::size_t или что-то подобное, что означает: не надо выводить тип, просто выберите тип, подходящий для любого разумного размера контейнера.

Это будет неподписанный, достаточно большой тип, поэтому у компилятора не будет соблазна кричать на вас о возможных потерях точности. В комментариях выше вы пишете, что size_t не гарантирует хорошую замену decltype(c.size()), но в то время как реализовать контейнер с индексом, несовместимым с size_t, не представляется возможным, такие индексы, безусловно, не будут числа (и, следовательно, несовместимы с i = 0), и контейнеры не будут иметь также метод size. Метод A size() подразумевает неотрицательный интеграл, и поскольку size_t предназначен для точных этих чисел, почти невозможно будет иметь контейнер размером, который не может быть представлен им.

Ваш второй вопрос направлен на то, как вывести тип, и вы уже предоставили самые легкие, но несовершенные ответы. Если вы хотите, чтобы решение было не таким подробным, как decltype и не удивительно читать как auto end, вы могли бы определить псевдоним шаблона и функцию генератора для начального индекса в каком-либо заголовке утилиты:

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

Если вы хотите иметь более общий индексный тип, например. для массивов и классов, которые не имеют метода size, он становится немного сложнее, потому что псевдонимы шаблонов не могут быть специализированными:

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; 
};

Однако, это много всего лишь для тех случаев, когда вам действительно нужен индекс, а простой цикл foreach недостаточен.

Ответ 2

Если c - контейнер, вы можете использовать container::size_type.

Ответ 3

На самом деле, я видел много раз (кашель llvm, clang), где они используют

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

Преимущество оценки End в начале заключается в том, что компилятор может быть уверен, что ему не нужно вызывать его каждый раз. Для коллекций, где вычисление конца тривиально, и компилятор уже может вывести, что ему не нужно вызывать end() несколько раз, это не поможет, но в других случаях это будет.

Или вы всегда можете использовать помощника:

Реализация enumerate_foreach на основе Boost foreach

Ответ 4

Хм... для этого требуется С++ 14 или компилятор, который поддерживает auto в параметрах лямбда. Если вы используете этот шаблон много, то полезная функция может быть полезна:

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);
    }
}

Использовать как:

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

Ответ 5

Вот приоритет, который я следую за

1) range-for
2) iterator/begin()/end() с типом, выведенным с помощью auto.

В случаях, когда требуется индексирование, что является предметом здесь, я предпочитаю использовать

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

Даже если я пропустил добавить u в 0, компилятор все равно предупредит меня.


Был бы любить decltype, если он не слишком подробный

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