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