Pro*_*ick 8 c++ stl c++20 std-ranges
C++20中的ranges库支持表达式
auto view = r | std::views::drop(n);
Run Code Online (Sandbox Code Playgroud)
使用范围适配器删除n范围的第一个元素。rdrop
但是,如果我从一个范围内递归删除元素,编译器就会进入一个无限循环。
最小工作示例:(在 GCC 10 中编译需要无限时间)
#include <ranges>
#include <iostream>
#include <array>
#include <string>
using namespace std;
template<ranges::range T>
void printCombinations(T values) {
if(values.empty())
return;
auto tail = values | views::drop(1);
for(auto val : tail)
cout << values.front() << " " << val << endl;
printCombinations(tail);
}
int main() {
auto range1 = array<int, 4> { 1, 2, 3, 4 };
printCombinations(range1);
cout << endl;
string range2 = "abc";
printCombinations(range2);
cout << endl;
}
Run Code Online (Sandbox Code Playgroud)
预期输出:
1 2
1 3
1 4
2 3
2 4
3 4
a b
a c
b c
Run Code Online (Sandbox Code Playgroud)
为什么这需要无限的时间来编译,我应该如何解决这个问题?
让我们看一下string案例(只是因为该类型较短)并手动检查调用堆栈。
printCombinations(range2)调用printCombinations<string>。该函数使用 递归调用自身tail。是什么类型的tail?那是drop_view<ref_view<string>>。所以我们调用printCombinations<drop_view<ref_view<string>>>. 直截了当到此为止。
现在,我们再次递归调用自己tail。tail 现在是什么类型?好吧,我们只是包装。它是drop_view<drop_view<ref_view<string>>>。然后我们再次递归drop_view<drop_view<drop_view<ref_view<string>>>>。然后我们再次递归drop_view<drop_view<drop_view<drop_view<ref_view<string>>>>>。如此循环下去,直到编译器爆炸。
我们可以通过维护相同的算法来解决这个问题吗?其实,是。P1739是关于减少这种模板实例化膨胀(尽管它没有像这个这样有趣的例子)。所以drop_view有一些特殊情况下的景色,它承认并不会重新包装。的类型"hello"sv | views::drop(1)是 still string_view,不是drop_view<string_view>。所以printCombinations(string_view(range2))应该只生成一个模板实例化。
但看起来 libstdc++ 还没有实现这个功能。因此,您可以手动实现它(但只能进行交易,例如,subrange)或在这里放弃递归方法。