Рекурсивное посещение `std::variable` с использованием лямбда-выражений и комбинаторов с фиксированной запятой
Я хотел бы посетить "рекурсивный" std::variant
использование лямбда-выражений и функций, создающих перегрузки (например,boost::hana::overload
)
Давайте предположим, что у меня есть вариант типа с именем my_variant
который может хранить один int
,float
илиvector<my_variant>
:
struct my_variant_wrapper;
using my_variant =
std::variant<int, float, std::vector<my_variant_wrapper>>;
struct my_variant_wrapper
{
my_variant _v;
};
(Я использую оберткуmy_variant_wrapper
класс, чтобы определить тип варианта рекурсивно.)
Я хочу рекурсивно посетить вариант, печатающий разные вещи в зависимости от сохраненных типов. Вот рабочий пример использованияstruct
посетитель:
struct struct_visitor
{
void operator()(int x) const { std::cout << x << "i\n"; }
void operator()(float x) const { std::cout << x << "f\n"; }
void operator()(const std::vector<my_variant_wrapper>& x) const
{
for(const auto& y : x) std::visit(*this, y._v);
}
};
призваниеstd::visit
С помощью вышеуказанного посетитель распечатывает желаемый результат:
my_variant v{
std::vector<my_variant_wrapper>{
my_variant_wrapper{45},
std::vector<my_variant_wrapper>{
my_variant_wrapper{1}, my_variant_wrapper{2}
},
my_variant_wrapper{33.f}
}
};
std::visit(struct_visitor{}, v);
// Prints:
/*
45i
1i
2i
33f
*/
Я хотел бы создать посетителя локально как серию перегруженных лямбд, используяboost::hana::overload
а такжеboost::hana::fix
,
fix
является реализацией Y-комбинатора, который может быть использован для реализации рекурсии в лямбдах, выведенных по типу. (См. Этот вопрос для получения дополнительной информации.)
Это то, что я пытался и ожидал работать:
namespace bh = boost::hana;
auto lambda_visitor = bh::fix([](auto self, const auto& x)
{
bh::overload(
[](int y){ std::cout << y << "i\n"; },
[](float y){ std::cout << y << "f\n"; },
[&self](const std::vector<my_variant_wrapper>& y)
{
for(const auto& z : y) std::visit(self, z._v);
})(x);
});
Мои рассуждения таковы:
boost::hana::fix
возвращает одинарную универсальную лямбду, которую можно использовать в качестве посетителяstd::variant
,boost::hana::fix
принимает двоичную обобщенную лямбду, где первый параметр является унарной функцией, которая допускает рекурсию лямбды, а второй параметр является начальным аргументом для тела лямбды.призвание
boost::hana::overload
с обработчиками для всех возможных типов внутриmy_variant
создает своего рода посетителя, который эквивалентенstruct_visitor
,С помощью
self
вместоlambda_visitor
внутриconst std::vector<my_variant_wrapper>&
перегрузка должна позволить рекурсии работать правильно.Немедленно вызывая созданную перегрузку с
bh::overload(...)(x)
должен вызвать рекурсивное посещение.
К сожалению, как вы можете видеть в этом примере Wandbox, lambda_visitor
пример не компилируется, извергая много почти неразборчивых ошибок, связанных с большими шаблонами:
...
/usr/local/boost-1.61.0/include/boost/hana/functional/fix.hpp:74:50: ошибка: использование 'main():: [с auto:2 = boost::hana::fix_t >; auto:3 = int]'перед удержанием' auto '{return f (fix (f), static_cast (x)...); }
...
Ошибка кажется похожей на то, что я получил бы без использования boost::hana::fix
:
auto lambda_visitor = bh::overload(
[](int y){ std::cout << y << "i\n"; },
[](float y){ std::cout << y << "f\n"; },
[](const std::vector<my_variant_wrapper>& y)
{
for(const auto& z : y) std::visit(lambda_visitor, z._v);
});
std::visit(lambda_visitor, v);
ошибка: использование lambda_visitor перед удержанием auto для (const auto& z: y) std::visit(lambda_visitor, z._v);
Что я делаю неправильно? Можно ли добиться локального рекурсивного варианта посещения с помощью fix
, overload
а набор лямбд?
Моя интуиция заключалась в том, что lambda_visitor
был бы "эквивалентен" struct_visitor
, благодаря косвенности, предлагаемой fix
,
1 ответ
Давайте выберем более простой пример. Мы хотим реализовать gcd
используя комбинатор с фиксированной точкой. Первым делом может быть что-то вроде:
auto gcd = bh::fix([](auto self, int a, int b) {
return b == 0 ? a : self(b, a%b);
});
std::cout << gcd(12, 18);
Это не скомпилируется с gcc, что в итоге приводит к этой ошибке:
/usr/local/boost-1.61.0/include/boost/hana/functional/fix.hpp:74:50: error: use of 'main()::<lambda(auto:2, int, int)> [with auto:2 = boost::hana::fix_t<main()::<lambda(auto:2, int, int)> >]' before deduction of 'auto'
{ return f(fix(f), static_cast<X&&>(x)...); }
^
Лямбда, к которой мы переходим fix()
имеет выведенный тип возврата. Но как мы можем это сделать? Есть только один оператор return, и он рекурсивный! Нам нужно помочь компилятору. Либо нам нужно разбить нашу return
утверждение, чтобы иметь четкий тип:
auto gcd = bh::fix([](auto self, int a, int b) {
if (b == 0) {
return a;
}
else {
return self(b, a%b);
}
});
или просто явно укажите тип возвращаемого значения:
auto gcd = bh::fix([](auto self, int a, int b) -> int {
return b == 0 ? a : self(b, a%b);
});
Оба эти варианта компилируются и работают.
То же самое относится и к вашему первоначальному примеру. Если вы просто укажите, что лямбда возвращает void
, все работает:
auto lambda_visitor = bh::fix([](auto self, const auto& x) -> void
// ^^^^^^^^
{
bh::overload(
[](int y){ std::cout << y << "i\n"; },
[](float y){ std::cout << y << "f\n"; },
[&self](const std::vector<my_variant_wrapper>& y)
{
for(const auto& z : y) std::visit(self, z._v);
})(x);
});
std::visit(lambda_visitor, v);