我一直在用优雅的(?)方式为std::variant
编写访问者,我不确定我在做什么是否有效的C ++(17),因为在Clang期间GCC是否会按照我的预期进行给我一个错误。在下文中,我首先提供一些背景信息,以使问题更易于理解。如果您之前已经看过这种事情,则可能想跳到最后一个问题。
写访问者的一个巧妙窍门包括从处理不同类型变量的一堆lambda派生出来,并通过可变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
情况的lambda中再次调用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 combinator:
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
通过递归应用{{1},实际上将两个自变量(函子self
和x
)的仿函数变成仅一个自变量x
的仿函数。 }。象征性地:
f
在C ++中(Yf)(x) = f(f(f(..., x), x), x)
的最小实现可能看起来像这样:
Y
新的元编程库template <typename F>
struct Y {
template <typename... X>
auto operator()(X &&... x) const {
return f(*this, std::forward<X>(x)...);
}
F f;
};
实际上提供了boost::hana
的标头,它们称为overload
,以及Y组合器的标头,它们称为fix
。他们的实现在Clang中遇到的错误与我的完全相同。
到目前为止,这正是我要实现的目标的逻辑。基于上述,我想到了这个minimum working example on Compiler Explorer。它可以在GCC 7和8上完美地编译,但对于Clang却可以提供错误信息
错误:函数'visit:38:11)>&,推断出返回类型的const std :: variant&>' 在定义之前使用
以及
错误:没有匹配函数可调用'const(lambda)类型的对象 在:38:11)'
visitor
最后进入 return f(*this, std::forward<X>(x)...);
^
:
错误:没有匹配的函数调用'__invoke'
<variant>
和
错误:constexpr变量“ _S_vtable”必须由 常数表达式
return std::__invoke(std::forward<_Visitor>(__visitor), ^~~~~~~~~~~~~
尤其是最后几个错误使我认为这可能是Clang错误? 有人能确认这确实是正确的C ++代码还是Clang在拒绝编译时是否正确?