Использование 'auto [...]' перед вычетом 'auto' с рекурсивным шаблоном функции на основе концепций
Я хотел создать deep_flatten
шаблон функции, который создаст range
элементов, которые глубоко join
изд. Например, если учесть только вложенныеstd::vector
s, я могу иметь:
template <typename T>
struct is_vector : public std::false_type { };
template <typename T, typename A>
struct is_vector<std::vector<T, A>> : public std::true_type { };
template <typename T>
auto deepFlatten(const std::vector<std::vector<T>>& vec) {
using namespace std::ranges;
if constexpr (is_vector<T>::value) {
auto range = vec | views::join;
return deepFlatten(std::vector(range.begin(), range.end()));
} else {
auto range = vec | views::join;
return std::vector(range.begin(), range.end());
}
}
Это позволяет мне делать:
std::vector<std::vector<std::vector<int>>> nested_vectors = {
{{1, 2, 3}, {4, 5}, {6}},
{{7}, {8, 9}, {10, 11, 12}},
{{13}}
};
std::ranges::copy(
deep_flatten(nested_vectors),
std::ostream_iterator<int>(std::cout, " ")
);
который, как и ожидалось, выводит в консоль следующий текст:
1 2 3 4 5 6 7 8 9 10 11 12 13
Но мне это решение не очень нравится. Это не только неэффективно (создание нескольких временных векторов), но и работает только сstd::vector
с. Я подумал, что могу использовать еще немного магии С ++20 и использоватьstd::ranges::range
концепция:
namespace rng {
template <std::ranges::range Rng>
auto deep_flatten(Rng&& rng) {
using namespace std::ranges;
if constexpr (range<Rng>) {
return deep_flatten(rng | views::join);
} else {
return rng | views::join;
}
}
}
Мне это показалось довольно простым - у нас есть std::ranges::range
и мы проверяем его тип значения. В зависимости от того, является ли это вложенным диапазоном, мы выполняем рекурсию или просто возвращаемjoin
элементы ed.
К сожалению, это не работает. После попытки запустить:
int main() {
std::set<std::vector<std::list<int>>> nested_ranges = {
{{1, 2, 3}, {4, 5}, {6}},
{{7}, {8, 9}, {10, 11, 12}},
{{13}}
};
std::ranges::copy(
rng::deep_flatten(nested_ranges),
std::ostream_iterator<int>(std::cout, " ")
);
}
Я получаю сообщение об ошибке:
In instantiation of 'auto rng::deep_flatten(Rng&&) [with Rng = std::ranges::join_view<std::ranges::ref_view<std::set<std::vector<std::__cxx11::list<int> > > > >]': required from 'auto rng::deep_flatten(Rng&&) [with Rng = std::set<std::vector<std::__cxx11::list<int> > >&]' required from here error: use of 'auto rng::deep_flatten(Rng&&) [with Rng = std::ranges::join_view<std::ranges::ref_view<std::set<std::vector<std::__cxx11::list<int> > > > >]' before deduction of 'auto' 39 | return deep_flatten(rng | views::join); | ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
Изучив похожие проблемы, я не могу понять, почему здесь появляется ошибка.
Я использую gcc version 10.1.0 (Rev3, Built by MSYS2 project)
1 ответ
Здесь есть две проблемы.
Первая проблема ваша:
namespace rng {
template <std::ranges::range Rng>
auto deep_flatten(Rng&& rng) {
using namespace std::ranges;
if constexpr (range<Rng>) { // <==
return deep_flatten(rng | views::join);
} else {
return rng | views::join;
}
}
}
Эта функция бесконечно рекурсивна. deep_flatten
сдерживается range<Rng>
, Итак if constexpr
check there всегда будет истинным, поэтому мы никогда не будем вводить базовый вариант. Это просто ошибка - мы проверяем не то, что не то, если мы диапазон, а если наше базовое значение является диапазоном. Это:
namespace rng {
template <typename Rng>
auto deep_flatten(Rng&& rng) {
using namespace std::ranges;
auto joined = rng | views::join;
if constexpr (range<range_value_t<decltype(joined)>>) {
return deep_flatten(joined);
} else {
return joined;
}
}
}
И здесь мы подходим ко второй проблеме, которая является проблемой стандартной библиотеки. Какиеrng | views::join
означает:
Имя
views::join
обозначает объект адаптера диапазона ([range.adaptor.object]). Учитывая подвыражениеE
, выражениеviews::join(E)
эквивалентно выражениюjoin_view{E}
.
Но join_view{E}
для E
это уже специализация join_view
... сейчас не работает из-за вывода аргументов шаблона класса (CTAD) - кандидат на вывод копии является лучшим кандидатом, поэтому наш вложенный join
операция фактически становится единой join
. Ваша первоначальная реализация обходит эту проблему, потому что это неjoin
-инг join_view
, это всегда join
-ing vector
с.
Я отправил LWG 3474.
А пока мы можем обойти views::join
проблема, просто используя join_view
и явно указав аргумент шаблона:
namespace rng {
template <typename Rng>
auto deep_flatten(Rng&& rng) {
using namespace std::ranges;
auto joined = join_view<views::all_t<Rng>>(rng);
if constexpr (range<range_value_t<decltype(joined)>>) {
return deep_flatten(joined);
} else {
return joined;
}
}
}
Это работает.