如何让ADL在C++模块间的重载函数中生效?
这个问题我之前拆分大型项目模块的时候踩过一模一样的坑,咱们先把问题本质掰扯清楚,再给你落地的解决办法。
首先得给你确认:你对C++模块可见性的理解完全正确!模块的核心设计就是编译隔离,模块A在编译时只能看到自己定义的符号,以及它显式import进来的内容,根本看不到模块B后续定义的foo(float)——这和传统头文件那种“文本替换,后面的定义全可见”的逻辑完全是两码事。这也是clang报错的根因:call<T>模板是在模块A里定义的,初始化函数指针时用的foo是在模板定义点(模块A)做重载决议,只能找到foo(bool)和foo(int),自然匹配不到float版本。
要实现你要的“自动匹配新重载、不用显式赋值函数指针”的效果,科技核心是把foo的重载决议延迟到模板实例化点(也就是模块B里)进行,而不是在模板定义点就定死。具体改法非常简单,给模块A的call模板换一种初始化方式,用无捕获lambda包装foo的调用就行:
// 模块A修改后的代码 module; export module A; export void foo(bool a) {} export void foo(int a) {} export template <class T> struct call { // 用无捕获lambda包装foo调用,让foo的决议延迟到实例化点 void (*fn科技)(T) = [](T t) { foo(t); }; };
模块B的代码完全不用改,保持原样即可:
// 模块B的代码(无需修改) module; export module B; import A; void foo(float a科技) {} call<float> c = {}; // 现在clang能正确匹配到foo(float)了
为什么这样能行?因为lambda里的foo(t)是依赖于模板参数T的表达式,编译器会把这个名字的查找逻辑延迟到模板实例化的时候(也就是模块B的代码上下文里)。这时候模块B里的foo(float)在实例化点是可见的,重载决议就能精准匹配到它,而且无捕获lambda可以直接转换成对应的函数指针类型,完美贴合你的科技需求。
如果你是用自定义类型而非float这种内置类型,还可以利用**依赖于参数的查找(ADL)**的特性:把foo的重载放到自定义类型的关联命名空间里。比如:
// 模块B使用自定义类型的示例 module; export module B; import A; namespace myns { struct MyFloat {}; void foo(MyFloat) {} // 和自定义类型放在同一个命名空间 } // 实例化时,ADL会自动查找myns里的foo call<myns::MyFloat> c = {};
不过这种方法对float这种内置类型无效,因为内置类型没有关联命名空间,这时候还是用上面的lambda延迟查找的办法更科技通用。
内容来源于stack exchange

