for constexpr

Сергей Тиунов
Сергей Тиунов

Мотивация

Бывают ситуации, когда было бы удобно организовать код в виде простого цикла (с известными пределами), но это сделать невозможно, потому что счетчик должен быть compile-time константой, например, он используется в виде параметра шаблона:

for (int i = 0; i < 3; i++) {
  some_template_function<i>(); // compilation error: 
  // variable with non-static storage duration cannot be used as a non-type argument
}

Примеры, когда нужно или желательно, чтобы счетчик цикла был compile-time константой:

  • использование в constexpr выражениях
  • использование в if-constexpr условиях
  • использование в качестве параметра шаблона
  • использование в качестве аргумента intrinsic-функции, которая требует immediate value (например, packed shuffle в x86)
  • либо мы просто хотим указать компилятору, чтобы цикл был развернут

Для того чтобы добиться подобного поведения в текущей версии С++, можно:

  1. использовать рекурсивные шаблоны или макросы - это не очень удачное решение, они нарушают локальность кода, т.е. их придется где-то определить, придумать им имя, а также они вызывают трудности при отладке, и вероятно не будут повторно использованы.
  2. variadic шаблоны или макросы - имеют те же недостатки.
  3. вручную развернуть (unroll) цикл - также не очень удачное решение, т.к. приводит к дублированию кода, проблемы которого широко известны.
  4. ???

Предложение

Вместо этого предлагается цикл с семантикой явного разворачивания: т.е. он записывается как цикл, но все управляющие конструкции (init statement, condition, iteration expression) вычисляются в compile-time. Тогда их можно использовать в качестве compile-time констант в constexpr выражениях, if-constexpr условиях, параметрах шаблонов и подобных контекстах. По аналогии с if-constexpr это могло бы выглядеть так:

for constexpr (int i = 0; i < 3; i++) {
  some_template_function<i>(); // i is constexpr - okay, should compile
}

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

some_template_function<0>();
some_template_function<1>();
some_template_function<2>();

... однако в отличие от него не содержит дублирования кода.

 

26
рейтинг
8 комментариев
Сергей Вахреев
Вроде for constexpr хотят внедрить для итерации по variadic templates...
Было бы почти красиво, если бы лямбды поддерживали template параметры: godbolt.org/g/yojZLK
Тогда можно было бы использовать if constexpr и другие compile time фичи. Хотя при -О3 компилятор идёт внутрь лямбд и оптимизирует код так же, как и при constexpr if в подобной реализации: godbolt.org/g/RSXOe8

Никто не подскажет, что с этим proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0428r0.pdf ?
Сергей Вахреев
Сергей Вахреев
С расширением templated lambdas от gcc: godbolt.org/g/sKvMQS (по вышеприведенному proposal)
Выбираемый диапазон индексов можно сделать по подобию boost::hana
Сергей Вахреев
Михаил Мальцев
Сергей Вахреев,
> Никто не подскажет, что с этим proposal
В Иссакуа его одобрили в EWG. В Коне у CWG возникла пара замечаний, которые предстоит исправить.
Михаил Мальцев
Игорь
Сергей Вахреев, лямбды всё уже поддерживают, в чём у вас проблема?
melpon.org/wandbox/permlink/jGbRr6PnePbZQAhz
Игорь
Игорь
Михаил Мальцев, печально, что одобрили. Мало зубодробительного контекстно-зависимой грамматики - давайте ещё усложним и накрутим лямбды :(
Воистину, каждый, предлагающий изменение в C++, для начала должен пожертвовать почку.
Игорь
Сергей Вахреев
Игорь, у меня никаких проблем нет. Просто не додумался / потратил мало времени на эксперименты / не знал. Выберите понравившийся вариант сами.
Сергей Вахреев
Михаил Мальцев
Уже есть аналогичное предложение "Tuple-based for loops": open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0589r0.pdf
Михаил Мальцев
Сергей Тиунов
Михаил Мальцев, согласен, предложение аналогичное в том плане, что варианты взаимозаменяемы:

auto indices = std::make_tuple(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
for (auto i : indices) { ... }

constexpr int size = std::tuple_size<decltype(tup)>::value;
for constexpr (int i = 0; i < size; i++) { auto elem = std::get<i>(tup); ... }

Однако мне кажется, что for constexpr в большинстве практических приложений будет удобнее, тогда как tuple-based for loop мне кажется удобным только вместе с рефлексией (например, напечатать все поля структуры).
Сергей Тиунов
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).