рекурсивное применение адаптера диапазона C++20 вызывает бесконечный цикл времени компиляции
Библиотека диапазонов в C++20 поддерживает выражение
auto view = r | std::views::drop(n);
убрать первый n
элементы диапазона r
с адаптером диапазона drop
.
Однако, если я рекурсивно отбрасываю элементы из диапазона, компилятор входит в бесконечный цикл.
Минимальный рабочий пример: (требуется бесконечное время для компиляции в 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
Почему для компиляции требуется бесконечное время и как мне решить проблему?
2 ответа
Давайте посмотрим на string
case (просто потому, что этот тип короче) и вручную проверьте стек вызовов.
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)
все еще string_view
не drop_view<string_view>
. ТакprintCombinations(string_view(range2))
должен генерировать только один экземпляр шаблона.
Но похоже, что libstdC++ еще не реализует эту функцию. Таким образом, вы можете реализовать его вручную (но только торговлю, скажем, subrange
) или отказаться от рекурсивного подхода.
Хотя это очень старый вопрос, но сегодня я столкнулся с этой досадной ошибкой/(или функцией), и именно так я решил ее без особых изменений в исходном коде.
#include <ranges>
#include <iostream>
#include <array>
#include <string>
#include <span> /////// <------ added this
using namespace std;
template<ranges::range T>
void printCombinations(T values_) { // <--- Changed values to values_
auto values = std::span(values_); // <--- defined values here
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;
}
Создавая диапазон, мы делаем переменную простоspan
вместо глубоко вложенныхtypename
s, как показано в принятом ответе.
Вы можете проверить это здесь, на goldbolt , что он работает именно так, как ожидалось.
ОБНОВЛЕНИЕ: вы можете заменить использование наstd::ranges::subrange
и это покроет еще больше случаев. Например,std::span
не работает сstd::ranges::views::reverse
.