Я играл с элегантными (?) Способами написания посетителей для 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 правильно отказался от компиляции?
Задача ещё не решена.
Других решений пока нет …