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