You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何仅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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 16:14:53