我正在尝试编写一个使用
std::visit
和递归 lambda 的跨平台库,但它仅在 macOS 上无法编译。错误部分最小化为以下代码:
// test.cpp
#include <iostream>
#include <variant>
using namespace std;
template <typename... Ts>
struct Overloaded : Ts... {
using Ts::operator()...;
};
template <typename Fn>
struct YCombinator {
Fn fn;
explicit YCombinator(Fn &&fn) : fn(std::forward<Fn>(fn)) {}
template <typename... Args>
inline decltype(auto) operator()(Args &&...args) const {
return fn(*this, std::forward<Args>(args)...);
}
};
// Deduction guide, not needed if using C++20
template <typename Fn>
YCombinator(Fn &&fn) -> YCombinator<Fn>;
int main(void) {
auto visitor = YCombinator(Overloaded{
[&](const auto &v, const int &i) -> void {
if (i) {
cout << "i: " << i << endl;
variant<char, int> b{char(i - 1)};
visit(v, b);
}
},
[&](const auto &v, const char &c) -> void {
if (c) {
cout << "c: " << int(c) << endl;
variant<char, int> b{int(c) - 1};
visit(v, b);
}
},
});
variant<char, int> a{char(10)};
visit(visitor, a);
return 0;
}
我在以下环境中测试了上面的代码:
g++ -std=c++20 test.cpp
clang++ -std=c++20 test.cpp
cl.exe /EHsc /std:c++20 test.cpp
clang++.exe -std=c++20 test.cpp
全部编译运行成功。但在 macOS 下,AppleClang 16.0.0 和 clang 18.1(通过 homebrew llvm 安装)会出现以下错误:
test.cpp:31:17: error: function 'visit<const YCombinator<Overloaded<(lambda at test.cpp:27:9), (lambda at test.cpp:34:9)>> &, std::variant<char, int> &, void>' with deduced return type cannot be used before it is defined
31 | visit(v, b);
| ^
test.cpp:17:16: note: in instantiation of function template specialization 'main()::(anonymous class)::operator()<YCombinator<Overloaded<(lambda at test.cpp:27:9), (lambda at test.cpp:34:9)>>>' requested here
17 | return fn(*this, std::forward<Args>(args)...);
| ^
/usr/local/opt/llvm@18/bin/../include/c++/v1/__type_traits/invoke.h:341:10: note: in instantiation of function template specialization 'YCombinator<Overloaded<(lambda at test.cpp:27:9), (lambda at test.cpp:34:9)>>::operator()<int &>' requested here
341 | decltype(std::declval<_Fp>()(std::declval<_Args>()...))
| ^
/usr/local/opt/llvm@18/bin/../include/c++/v1/__type_traits/invoke.h:351:19: note: while substituting deduced template arguments into function template '__invoke' [with _Fp = const YCombinator<Overloaded<(lambda at test.cpp:27:9), (lambda at test.cpp:34:9)>> &, _Args = <int &>]
351 | static decltype(std::__invoke(std::declval<_XFp>(), std::declval<_XArgs>()...)) __try_call(int);
| ^
/usr/local/opt/llvm@18/bin/../include/c++/v1/__type_traits/invoke.h:357:28: note: while substituting deduced template arguments into function template '__try_call' [with _XFp = const YCombinator<Overloaded<(lambda at test.cpp:27:9), (lambda at test.cpp:34:9)>> &, _XArgs =(no value)]
357 | using _Result = decltype(__try_call<_Fp, _Args...>(0));
| ^
/usr/local/opt/llvm@18/bin/../include/c++/v1/__type_traits/invoke.h:428:68: note: in instantiation of template class 'std::__invokable_r<void, const YCombinator<Overloaded<(lambda at test.cpp:27:9), (lambda at test.cpp:34:9)>> &, int &>' requested here
428 | struct _LIBCPP_TEMPLATE_VIS is_invocable : integral_constant<bool, __invokable<_Fn, _Args...>::value> {};
| ^
/usr/local/opt/llvm@18/bin/../include/c++/v1/__type_traits/invoke.h:434:40: note: (skipping 28 contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all)
434 | inline constexpr bool is_invocable_v = is_invocable<_Fn, _Args...>::value;
| ^
/usr/local/opt/llvm@18/bin/../include/c++/v1/variant:500:32: note: in instantiation of function template specialization 'std::__variant_detail::__visitation::__base::__make_fmatrix<std::__variant_detail::__visitation::__variant::__value_visitor<YCombinator<Overloaded<(lambda at test.cpp:27:9), (lambda at test.cpp:34:9)>> &> &&, std::__variant_detail::__base<std::__variant_detail::_Trait::_TriviallyAvailable, char, int> &>' requested here
500 | constexpr auto __fmatrix = __make_fmatrix<_Visitor&&, decltype(std::forward<_Vs>(__vs).__as_base())...>();
| ^
/usr/local/opt/llvm@18/bin/../include/c++/v1/variant:586:20: note: in instantiation of function template specialization 'std::__variant_detail::__visitation::__base::__visit_alt<std::__variant_detail::__visitation::__variant::__value_visitor<YCombinator<Overloaded<(lambdaat test.cpp:27:9), (lambda at test.cpp:34:9)>> &>, std::__variant_detail::__impl<char, int> &>' requested here
586 | return __base::__visit_alt(
| ^
/usr/local/opt/llvm@18/bin/../include/c++/v1/variant:598:12: note: in instantiation of function template specialization 'std::__variant_detail::__visitation::__variant::__visit_alt<std::__variant_detail::__visitation::__variant::__value_visitor<YCombinator<Overloaded<(lambda at test.cpp:27:9), (lambda at test.cpp:34:9)>> &>, std::variant<char, int> &>' requested here
598 | return __visit_alt(__make_value_visitor(std::forward<_Visitor>(__visitor)), std::forward<_Vs>(__vs)...);
| ^
/usr/local/opt/llvm@18/bin/../include/c++/v1/variant:1561:21: note: in instantiation of function template specialization 'std::__variant_detail::__visitation::__variant::__visit_value<YCombinator<Overloaded<(lambda at test.cpp:27:9), (lambda at test.cpp:34:9)>> &, std::variant<char, int> &>' requested here
1561 | return __variant::__visit_value(std::forward<_Visitor>(__visitor), std::forward<_Vs>(__vs)...);
| ^
test.cpp:43:10: note: in instantiation of function template specialization 'std::visit<YCombinator<Overloaded<(lambda at test.cpp:27:9), (lambda at test.cpp:34:9)>> &, std::variant<char, int> &, void>' requested here
43 | std::visit(visitor, a);
| ^
/usr/local/opt/llvm@18/bin/../include/c++/v1/variant:1558:1: note: 'visit<const YCombinator<Overloaded<(lambda at test.cpp:27:9), (lambda at test.cpp:34:9)>> &, std::variant<char, int> &, void>' declared here
1558 | visit(_Visitor&& __visitor, _Vs&&... __vs) {
| ^
1 error generated.
那么为什么会出现这个错误,有没有办法解决它(最好不要彻底改变代码模式)?
我猜 macOS 上的 clang 目前在返回类型推导方面存在一些问题。
根据@MarekR的发现,一种解决方法是手动为YCombinator扣除带有
type_traits
的类型,即将问题中的YCombinator替换为
template <typename Fn>
struct YCombinator {
Fn fn;
explicit YCombinator(Fn &&fn) : fn(std::forward<Fn>(fn)) {}
template <typename... Args>
inline invoke_result_t<Fn, YCombinator &, Args...>
operator()(Args &&...args) const {
return fn(*this, std::forward<Args>(args)...);
}
};
然后代码将通过 macOS clang>=17 的编译。