for 语句中的 constexpr

问题描述 投票:0回答:4

提供了

if constexpr
,其中:

条件的值必须是上下文转换的

bool
类型常量表达式。如果值为
true
,则丢弃statement-false(如果存在),否则,丢弃statement-true

有没有办法在

for
语句中使用它?在编译时展开循环?我希望能够做这样的事情:

template <int T>
void foo() {
   for constexpr (auto i = 0; i < T; ++i) cout << i << endl;
}
c++ for-loop c++17 template-meta-programming if-constexpr
4个回答
11
投票

有没有办法在 for 语句中使用它?在编译时展开循环?我希望能够做这样的事情

我不这么简单地思考。

但是,如果您能负担得起辅助函数,使用

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

5
投票

如果编译器知道循环限制,编译器将展开循环(如果它发现它有益的话)。并非所有循环展开都是有益的!关于循环展开的好处,您不太可能比编译器做出更好的决定。

不需要

constexpr for
(如您所说),因为
constexpr if
正在启用功能 - 您可以将导致程序格式错误的代码放入
constexpr if
错误分支内 - 这不是纯粹的优化。

另一方面,

Constexpr for
将是纯粹的优化(至少按照您的描述,不计算执行0次循环的边缘情况),因此最好保留“as-if”优化规则。


4
投票

没有帮助代码就不行。

#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...
序列并生成其无符号整数表示形式。


0
投票

作为 @Yakk 答案的替代方案,https://artificial-mind.net/blog/2020/10/31/constexpr-for 提供了一个更简单的解决方案。

template <auto Start, auto End, auto Inc = 1, class F>
constexpr void constexpr_for(F&& f) {
    static_assert(Inc != 0);
    if constexpr ((Inc > 0 && Start < End) || (Inc < 0 && End < Start)) {
        f(std::integral_constant<decltype(Start), Start>());
        constexpr_for<Start + Inc, End, Inc>(f);
    }
}

用途:

constexpr_for<0,5>([&](auto i) { dostuff(i); });

您甚至可以像这样改变它,以便它自动推导开始和结束。

template <auto Start, auto End, auto Inc = 0, class F>
constexpr void constexpr_for(F&& f) {
    constexpr auto dir = (Inc != 0) ? Inc: (Start > End) ? -1 : 1; 
    constexpr auto next = Start + dir;
    if constexpr ((dir > 0 && Start < End) || ((dir < 0 && End < Start))) {
        f(std::integral_constant<decltype(Start), Start>());
        if constexpr ((dir > 0 && next < End) || (dir < 0 && End < next)) {
            constexpr_for<next, End, Inc>(f);
        }
    }
}

用途:

constexpr_for<4,-1>([&](auto i) { 
    dostuff(i); 
    // 4 3 2 1 0
});
© www.soinside.com 2019 - 2024. All rights reserved.