Рекурсивное посещение `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);
Другие вопросы по тегам