Рекурсивное посещение с помощью Y комбинатора - должно ли оно компилироваться? - PullRequest
0 голосов
/ 11 октября 2018

Я баловался с элегантными (?) Способами написания посетителей для 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 выдает ошибки

error: function 'visit: 38: 11)> &, const std :: variable &>' с выведенным типом возвратане может использоваться до того, как он будет определен

, а также

ошибка: нет соответствующей функции для вызова объекта типа '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();
                        ^           ~~~~~~~~~~

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

...