使用用户定义函数展开泛型循环
generic loop-unrolling with user-defined function
最近我决定制作循环展开器。我的函数从_Beg迭代到_End(这是模板参数),并在每个索引上调用函数_func:
template<size_t _Beg, size_t _End, typename _Func>
typename std::enable_if<_Beg < _End, void>::type
for_r(_Func _func)
{
_func(_Beg);
for_r<_Beg+1, _End>(_func);
}
template<size_t _Beg, size_t _End, typename _Func>
typename std::enable_if<_Beg >= _End, void>::type
for_r(_Func _func)
{}
它可以这样工作:
for_r<0, 10>([](size_t index){cout << index << endl;});
但是,"index"变量在编译时是已知的,因此从逻辑上讲,可以在lambda中使用"index"作为常量表达式。像这样:
tuple<int, int, int, int> tpl(1, 2, 3, 4);
for_r<0, 4>([&](size_t index){cout << get<index>(tpl) << endl;});
但是"index"是可变的,不可能将其作为constexpr传递到lambda中。有没有办法在不显式键入循环展开的情况下处理它并实现逻辑行为:
cout << get<0>(tpl) << endl << get<1>(tpl) << endl << get<2>(tpl) << endl << get<3>(tpl) << endl;
由于7年过去了,现在我们有了很好的功能,如C++17的折叠表达式和C++20的显式模板化lambdas,现在可以编写和使用这样的"constexpr表示"-喜欢事物很容易。例如:
// Offsets std::integer_sequence by OFFSET
template <typename T, T OFFSET, T... IDXS>
constexpr std::integer_sequence<T, OFFSET + IDXS...> offset(
std::integer_sequence<T, IDXS...>)
{
return {};
}
// Calls templated operator() of given function object
// with INDEX template parameter from range [START; END)
template<std::integral T, T START, T END>
void for_constexpr_interval(auto iteration)
{
if constexpr (START >= END)
return;
[&]<T... IDXS>(std::integer_sequence<T, IDXS...>) {
(iteration.template operator()<IDXS>(), ...);
}(offset<T, START>(std::make_integer_sequence<T, END - START>()));
}
在for_constexpr_interval
中,我们使用显式模板化的立即调用lambda,通过从std::integer_sequence
参数(在std::make_integer_sequence
之上的offset
函数的帮助下创建,以表示[START; END)
间隔)推导引入模板参数包IDXS
,同时避免不必要的辅助函数污染命名空间。这个包现在可以直接由fold表达式使用,而不需要使用递归实例化。在该fold表达式中,我们使用适当的模板参数调用iteration
函数对象的模板化operator()
。需要语法.template operator()
来消除operator()
表示模板的歧义,使得例如后面的<
不被解释为";小于";
它可以这样使用:
std::tuple tpl(1, 2, 3, 4);
for_constexpr_interval<size_t, 0, 4>([&]<size_t INDEX>() {
if constexpr (INDEX > 0)
std::cout << ' ';
std::cout << std::get<INDEX>(tpl);
});
std::cout << std::endl;
for_constexpr_interval<int, -1, 2>([&]<int INDEX>() {
std::cout << INDEX << std::endl;
});
Godbolt
请注意,当将函数对象传递给for_constexpr_interval
时,我们再次使用显式模板化的lambda。此外,从生成的程序集中可以看出,即使在-O1
,main
中的优化调用也会内联,这至少在最新的GCC、Clang和MSVC中会发生。如果需要,编译器特定的属性/说明符可以强制内联(在for_constexpr_interval
、内部立即调用的lambda、传递的lambda的任何组合上),尽管通常这样的决策最好留给编译器。
最后,for_constexpr_interval
可以进一步更改/广义/补充,例如,通过允许索引、STEP
(增量)参数或非std::integral
索引类型的反向遍历,但这将需要更复杂的实现来在内部lambda的参数中创建索引序列(可能会放弃std::make_integer_sequence
以进行自定义替换),并对浮点索引进行一些维护。
- 错误处理.将系统错误代码映射到泛型
- 如果有一个模板构造函数只有一个泛型参数,为什么我必须有一个复制构造函数
- 链表的泛型函数remove()与成员函数remove)
- 给定一个类型,如何派生一个泛型更广泛的类型(例如,用于溢出安全求和)?
- 模板化接口 - 创建一个泛型模板类以返回任何容器
- 如何编写将要继承的泛型代码?
- C++17 如何保存泛型可调用对象以供以后使用
- 使用宏扩展的泛型:为什么指令缓存使用不当?
- C++泛型类错误,问题出在哪里?
- C++泛型类,单独实现?
- 将参数传递给泛型 lambda 时复制构造函数不正确
- 泛型枚举和其他类型的重载模板函数
- 使用泛型类型推送到堆栈时出现问题
- 可变参数泛型 lambda 和函数重载
- C++ 泛型和多态性:这种模式可行吗?
- 这些语句是否等效(静态变量、常量变量和泛型)
- 在 stl 映射和列表 (c++) 上进行迭代的泛型循环
- 无法在循环中实例化泛型函数
- 使用用户定义函数展开泛型循环
- 基本类型和复杂类型的循环的泛型