C++将类的模板友元函数作为参数传入其他函数时报错
编译错误原因
你的推测方向是对的,问题核心出在类内友元的名称查找规则上,两个核心错误点:
- 你在类模板
A内定义的foo不是命名空间test下的普通函数/函数模板:它没有独立的模板参数列表,是依附于A的实例化生成的普通函数——也就是当你实例化A<int>时,编译器才会在test命名空间生成一个参数为const A<int>&的普通foo函数,这个函数不存在foo<int>这种显式传模板实参的用法。 - 这种类内定义的友元属于ADL可见隐藏名称:它会把函数名注入到外围的
test命名空间,但仅当函数调用的实参类型关联到test命名空间/类A时,才会被参数依赖查找找到;常规的非限定名查找、带命名空间前缀的限定名查找(比如test::foo)都会直接跳过这个名称,根本定位不到函数实体。
你写的两行报错代码刚好踩了这两个规则:
- 第一行
foo<int>:非限定名查找找不到隐藏的友元foo,同时foo本身不是模板,加<int>模板实参本身就是非法写法 - 第二行
test::foo<int>:限定名查找直接禁用ADL,在test命名空间里找不到公开声明的foo模板,同样报错
问题复现代码
#include <functional> namespace test { template <class T> class A { public: friend auto foo(const A& obj) { return 1; } }; template <class Function, class... Args> void bar(Function f, Args&&... args) { const auto result = std::invoke(f, std::forward<Args>(args)...); // do stuff with result } } // namespace test auto main() -> int { test::A<int> value; test::bar(foo<int>, value); // 编译错误 test::bar(test::foo<int>, value); // 编译错误 return 0; }
修复方案
根据你的使用场景二选一即可:
方案1:保留友元隐藏特性,用lambda包装触发ADL
如果你就是要保留类内友元的隐藏特性、不想把foo暴露为命名空间公开接口,不要直接把foo作为参数传给bar,用lambda包一层调用即可——lambda内部调用foo时传入A<int>类型的实参,会正常触发ADL找到隐藏的友元函数:
auto main() -> int { test::A<int> value; test::bar([](const auto& obj) { return foo(obj); }, value); return 0; }
方案2:将foo声明为命名空间级别的函数模板
如果你确实需要直接取foo的地址、显式指定模板实参,就把foo从类内友元改成命名空间下公开声明的函数模板,类内只保留友元声明即可:
#include <functional> namespace test { // 前置声明类模板 template <class T> class A; // 前置声明函数模板 template <class T> auto foo(const A<T>& obj); template <class T> class A { public: // 友元声明,绑定对应实例化的foo模板 friend auto foo<T>(const A<T>& obj); }; // 命名空间级别实现foo模板 template <class T> auto foo(const A<T>& obj) { return 1; } template <class Function, class... Args> void bar(Function f, Args&&... args) { const auto result = std::invoke(f, std::forward<Args>(args)...); // do stuff with result } } // namespace test auto main() -> int { test::A<int> value; // 两种写法都可以正常编译 test::bar(foo<int>, value); test::bar(test::foo<int>, value); return 0; }
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

