为何仅AppleClang无法编译含递归lambda的std::visit代码并报错?
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; }
测试环境
- Linux:GCC 14.2.1(
g++ -std=c++20 test.cpp)、Clang 18.1.8(clang++ -std=c++20 test.cpp) - Windows:VS2022+MSVC14.40(
cl.exe /EHsc /std:c++20 test.cpp)、Clang19.1.0(clang++.exe -std=c++20 test.cpp) - macOS:AppleClang16.0.0、Homebrew Clang18.1(编译失败)
编译错误信息
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<(lambda at 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.
错误原因
这是libc++(LLVM的C标准库)的模板实例化顺序严格性导致的问题。当递归调用std::visit时,libc在推导返回类型的过程中,会提前检查YCombinator的调用可行性,此时递归路径中的std::visit特化还未完全定义,触发了"带推导返回类型的函数在定义前无法使用"的错误。而GCC使用的libstdc++、MSVC的标准库模板实例化逻辑没有这个严格的顺序检查,因此能正常编译。
解决方法
不需要大幅修改递归Lambda+YCombinator的核心模式,有两种简单的修复方式:
方法1:显式指定std::visit的返回类型
在递归调用visit时,显式指定模板参数,避免返回类型推导:
// 原代码中的visit(v, b); 修改为: std::visit<void>(v, b);
这样编译器无需推导返回类型,直接使用显式指定的void,绕过libc++的提前检查。
方法2:修改YCombinator的operator()返回类型
将YCombinator的operator()返回类型从decltype(auto)改为显式的void(因为访问器返回值都是void):
template <typename... Args> inline void operator()(Args &&...args) const { return fn(*this, std::forward<Args>(args)...); }
这种方式让libc++在检查调用可行性时,无需推导返回类型,直接确定返回值为void,避免错误。
两种方法都能在不改变核心代码结构的前提下,解决macOS下的编译问题,同时保持其他平台的兼容性。
内容的提问来源于stack exchange,提问作者R-R
相关产品推荐
相关产品推荐

