constexpr в for-заявлении

C++17 обеспечивает if constexpr, в котором:

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

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

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

3 ответа

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

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

Но если вы можете позволить себе вспомогательную функцию, с std::integer_sequence и инициализация неиспользуемого массива целых чисел в стиле C, начиная с 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), ...); }

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

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

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

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

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

или же

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... и создает целое число без знака.

Другие вопросы по тегам