Рекурсивное посещение с помощью Y комбинатора — должно ли оно компилироваться?

Я играл с элегантными (?) Способами написания посетителей для std::variant и я не уверен, что то, что я делаю, является допустимым C ++ (17), поскольку GCC будет делать именно так, как я намереваюсь, в то время как Clang выдает мне ошибку. Ниже я сначала приведу некоторый контекст, чтобы сделать вопрос более доступным; Вы можете перейти к концу для реального вопроса, если вы видели подобные вещи раньше.


Аккуратный трюк для написания посетителей включает в себя вариационный вывод из набора лямбд, которые обрабатывают различные варианты типов, и перегрузку их операторов вызова с помощью вариационных using:

template <typename... Base>
struct visitor : Base... {
using Base::operator()...;
};

Одной из областей, где это становится немного громоздким, является рекурсивное посещение вариантов типов, которые содержат коллекции самих себя. Рассмотрим, например, этот вариант типа var который может быть int или вектор сам по себе:

struct var_vec; // forward declaration
using var = std::variant<int, var_vec>;
struct var_vec : std::vector<var> {
using std::vector<var>::vector;
};

Нельзя просто призвать std::visit снова внутри лямбда обработки var_vec дело, потому что visitor объект является неполным типом в тот момент времени:

visitor{
[](int i) -> void { std::cout << i << std::endl; },
[](var_vec const & x) -> void {
for (var const & y : x)
std::visit(/* ? */, y);
}
};

К сожалению, есть довольно красивое решение этой головоломки, которая включает в себя Y комбинатор:

Y{[](auto const & self, auto const & x) -> void {
visitor{
[](int i) -> void { std::cout << i << std::endl; },
[&self](var_vec const & x) -> void {
for (var const & y : x)
std::visit(self, y);
}
}(x);
}}

где Y по существу превращает функтор двух аргументов (функтор self а также x) в функтор только одного аргумента x рекурсивно применяя f, Символично:

(Yf)(x) = f(f(f(..., x), x), x)

Минимальная реализация Y в C ++ может выглядеть так:

template <typename F>
struct Y {
template <typename... X>
auto operator()(X &&... x) const {
return f(*this, std::forward<X>(x)...);
}
F f;
};

Новая библиотека метапрограммирования boost::hana на самом деле предоставляет заголовки для visitor, который они называют overload, и для комбинатора Y, который они называют fix. Их реализации терпят те же ошибки с Clang, что и мои.


Пока что логика того, чего я пытаюсь достичь. Исходя из вышеизложенного, я придумал это минимальный рабочий пример в Compiler Explorer. Он компилируется без ошибок на GCC 7 и 8, но для Clang выдает ошибки

ошибка: функция ‘визит: 38: 11)> &, const std :: вариант &> ‘с выведенным типом возврата не может
использоваться до того, как оно будет определено

так же как

ошибка: нет подходящей функции для вызова объекта типа ‘const (лямбда
в 38:11)

    return f(*this, std::forward<X>(x)...);
^

и наконец в <variant>:

ошибка: не найдена соответствующая функция для вызова __invoke

    return std::__invoke(std::forward<_Visitor>(__visitor),
^~~~~~~~~~~~~

а также

ошибка: переменная constexpr ‘_S_vtable’ должна быть инициализирована
постоянное выражение

  static constexpr auto _S_vtable = _S_apply();
^           ~~~~~~~~~~

Особенно последние несколько ошибок заставляют меня думать, что это, вероятно, ошибка Clang? Кто-нибудь может подтвердить, что это действительно правильный код C ++ или Clang правильно отказался от компиляции?

0

Решение

Задача ещё не решена.

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]