c++17提供了
if constexpr
,其中:
条件的值必须是上下文转换的
类型常量表达式。如果值为bool
,则丢弃statement-false(如果存在),否则,丢弃statement-truetrue
有没有办法在
for
语句中使用它?在编译时展开循环?我希望能够做这样的事情:
template <int T>
void foo() {
for constexpr (auto i = 0; i < T; ++i) cout << i << endl;
}
有没有办法在 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), ...); }
如果编译器知道循环限制,编译器将展开循环(如果它发现它有益的话)。并非所有循环展开都是有益的!关于循环展开的好处,您不太可能比编译器做出更好的决定。
不需要
constexpr for
(如您所说),因为constexpr if
正在启用功能 - 您可以将导致程序格式错误的代码放入constexpr if
错误分支内 - 这不是纯粹的优化。
另一方面,Constexpr for
将是纯粹的优化(至少按照您的描述,不计算执行0次循环的边缘情况),因此最好保留“as-if”优化规则。
没有帮助代码就不行。
#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...
序列并生成其无符号整数表示形式。
作为 @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
});