【问题标题】:recursive application of C++20 range adaptor causes a compile time infinite loopC++20 范围适配器的递归应用导致编译时无限循环
【发布时间】:2020-09-04 03:15:46
【问题描述】:

C++20 中的范围库支持表达式

auto view = r | std::views::drop(n);

使用范围适配器 drop 删除范围 r 的第一个 n 元素。

但是,如果我递归地从一个范围中删除元素,编译器会进入一个无限循环。


最小的工作示例:(在 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;
}

预期输出:

1 2
1 3
1 4
2 3
2 4
3 4

a b
a c
b c

为什么编译需要无限时间,我应该如何解决这个问题?

【问题讨论】:

    标签: c++ stl c++20 std-ranges


    【解决方案1】:

    让我们看一下string 的情况(只是因为该类型更短)并手动检查调用堆栈。

    printCombinations(range2) 呼叫printCombinations&lt;string&gt;。该函数使用tail 递归调用自身。 tail 的类型是什么?那是drop_view&lt;ref_view&lt;string&gt;&gt;。所以我们打电话给printCombinations&lt;drop_view&lt;ref_view&lt;string&gt;&gt;&gt;。到目前为止很简单。

    现在,我们再次以tail 递归调用自己。 tail现在是什么类型的?好吧,我们只是包装。这是drop_view&lt;drop_view&lt;ref_view&lt;string&gt;&gt;&gt;。然后我们用drop_view&lt;drop_view&lt;drop_view&lt;ref_view&lt;string&gt;&gt;&gt;&gt; 再次递归。然后我们用drop_view&lt;drop_view&lt;drop_view&lt;drop_view&lt;ref_view&lt;string&gt;&gt;&gt;&gt;&gt; 再次递归。以此类推,无限循环,直到编译器爆炸。

    我们可以通过保持相同的算法来解决这个问题吗?其实,是。 P1739 是关于减少这种模板实例化膨胀(尽管它没有像这个那样有趣的例子)。所以drop_view 有一些special cases 用于它识别并且不会重新包装的视图。 "hello"sv | views::drop(1) 的类型仍然是 string_view,而不是 drop_view&lt;string_view&gt;。所以printCombinations(string_view(range2)) 应该只生成一个模板实例化。

    但似乎 libstdc++ 还没有实现这个功能。因此,您可以手动实现它(但只能在 subrange 中进行交易)或在此处放弃递归方法。

    【讨论】:

    • 有没有办法使用更具体的模板来实现values.empty() 基本案例?像template&lt;range::empty_range T&gt; void printCombination(T values) {} 这样的东西?如果允许这样的模板,它可以终止递归。
    • @ProgrammierPatrick 这些范围的大小是运行时属性,所以不是。
    • 有道理。感谢您的帮助
    猜你喜欢
    • 2021-04-11
    • 2015-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-19
    • 2017-07-27
    相关资源
    最近更新 更多