constexpr в for-Statement

c++17 предоставляет if constexpr, в котором:

значение условия должно быть контекстно преобразованным постоянным выражением типа bool. Если значение равно true, то оператор-ложь отбрасывается (если присутствует), в противном случае отбрасывается оператор-истина.

Есть ли способ использовать это и в for-операторе? Развернуть цикл во время компиляции? Я хотел бы иметь возможность сделать что-то вроде этого:

template <int T>
void foo() {
   for constexpr (auto i = 0; i < T; ++i) cout << i << endl;
}

person Jonathan Mee    schedule 21.02.2018    source источник
comment
Я не вижу if constexpr в вашем коде..   -  person Jesper Juhl    schedule 21.02.2018
comment
@JesperJuhl Тьфу, спасибо, я хотел привести пример того, как я хотел бы использовать for constexpr   -  person Jonathan Mee    schedule 21.02.2018
comment
stackoverflow.com/questions/45758545/ stackoverflow.com/questions/6872919/compile-time-loops Помогут ли вам ответы на эти вопросы или Вы хотите явно использовать оператор for для цикла?   -  person Sebi    schedule 21.02.2018
comment
Нет, for constexpr нет. Я видел это обсуждение на стандартном -предложения в группе Google, хотя   -  person Justin    schedule 21.02.2018


Ответы (3)


Есть ли способ использовать это в for-statement? Развернуть цикл во время компиляции? Я хотел бы иметь возможность сделать что-то вроде этого

Я не думаю так просто.

Но если вы можете позволить себе вспомогательную функцию с std::integer_sequence и инициализацией неиспользуемого массива целых чисел в стиле C, начиная с С++ 14, вы можете сделать следующее:

#include <utility>
#include <iostream>

template <int ... Is>
void foo_helper (std::integer_sequence<int, Is...> const &)
 {
   using unused = int[];

   (void)unused { 0, (std::cout << Is << std::endl, 0)... };
 }

template <int T>
void foo ()
 { foo_helper(std::make_integer_sequence<int, T>{}); }

int main ()
 {
   foo<42>();
 }

Если вы можете использовать C++17, вы можете избежать массива unused и, используя свертку, foo_helper() можно просто записать следующим образом

template <int ... Is>
void foo_helper (std::integer_sequence<int, Is...> const &)
 { ((std::cout << Is << std::endl), ...); }
person max66    schedule 21.02.2018
comment
Что ж... На самом деле я не искал способ улучшить свой игрушечный пример, но, боже мой, это увлекательно. Если вы можете объяснить, что здесь делает unused, это стоит +1 от меня. - person Jonathan Mee; 21.02.2018
comment
@JonathanMee - В том смысле, что я добавил упрощение C ++ 17. В любом случае... unused - это просто массив в стиле C, объявленный только для инициализации нулями. Но, используя мощь оператора запятой, вы можете добавить часть std::cout перед запятой, чтобы std::cout выполнялось, но не влияло на значения массива. Другими словами, unused — это всего лишь уловка, позволяющая создать среду для распаковки значений Is.... - person max66; 21.02.2018

Если пределы цикла известны компилятору, компилятор развернет цикл, если сочтет это полезным. Не все развертывания циклов полезны! И вряд ли вы примете лучшее решение, чем компилятор, в отношении преимуществ развертывания цикла.

Нет необходимости в constexpr for (как вы выразились), так как constexpr if включает функцию - вы можете поместить код, который сделает программу неправильной внутри constexpr if ложной ветки - это не чистая оптимизация.

Constexpr for, с другой стороны, будет чистой оптимизацией (по крайней мере, как вы ее описываете, не считая второстепенного случая цикла, выполненного 0 раз), и поэтому его лучше оставить для правил оптимизации «как если бы».

person SergeyA    schedule 21.02.2018

Не без вспомогательного кода.

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;

template<std::size_t...Is>
constexpr auto index_over( std::index_sequence<Is...> ) noexcept(true) {
  return [](auto&& f)
    RETURNS( decltype(f)(f)( index_t<Is>{}... ) );
}
template<std::size_t N>
constexpr auto index_upto( index_t<N> ={} ) noexcept(true) {
  return index_over( std::make_index_sequence<N>{} );
}

template<class F>
constexpr auto foreacher( F&& f ) {
  return [&f](auto&&...args) noexcept( noexcept(f(args))&&... ) {
    ((void)(f(args)),...);
  };
}

это наша сантехника.

template<int T>
void foo() {
  index_upto<T>()(
    foreacher([](auto I){
      std::cout << I << "\n";
    })
  );
}

цикл времени компиляции со значениями на каждом шаге.

Или мы можем скрыть детали:

template<std::size_t start, std::size_t length, std::size_t step=1, class F>
constexpr void for_each( F&& f, index_t<start> ={}, index_t<length> ={}, index_t<step> ={} ) {
  index_upto<length/step>()(
    foreacher([&](auto I){
      f( index_t<(I*step)+start>{} );
    })
  );
}

тогда мы получим:

for_each<0, T>([](auto I) {
  std::cout << I << "\n";
});

or

for_each([](auto I) {
  std::cout << I << "\n";
}, index_t<0>{}, index_t<T>{});

это можно дополнительно улучшить с помощью определяемых пользователем литералов и переменных шаблона:

template<std::size_t I>
constexpr index_t<I> index{};

template<char...cs>
constexpr auto operator""_idx() {
  return index< parse_value(cs...) >;
}

где parse_value — это функция constexpr, которая принимает последовательность char... и создает ее целочисленное представление без знака.

person Yakk - Adam Nevraumont    schedule 21.02.2018