C++20:如何让Concept兼容后续定义的自由函数?
现有一个用于约束通用算法algo的Concept MyConcept,要求类型存在接受该类型并返回同类型的自由函数my_free_function。当前代码组织要求先引入包含Concept和算法的头文件,后续再定义满足约束的函数,但unsigned long和caz::NativeU64的调用编译失败,报错提示无法找到my_free_function。
原代码实现
#include <concepts> template<class T> concept MyConcept = requires (T a) { { my_free_function(a) } -> std::same_as<T>; }; template<MyConcept T> auto algo(T& a) { return my_free_function(a); } namespace baz { struct bar {}; bar my_free_function(bar) { return bar{}; } }; // baz unsigned long my_free_function(unsigned long) { return 42; } namespace caz { using NativeU64 = unsigned long; NativeU64 my_free_function(NativeU64) { return NativeU64{42ul}; } }; // caz
调用代码
auto a = baz::bar{}; algo(a); auto b = 1ul; algo(b); caz::NativeU64 c = 1ul; algo(c);
编译器错误信息
'my_free_function(a)' would be invalid: call to function
'my_free_function' that is neither
visible in the template definition nor found by argument-dependent lookup
{ my_free_function(a) } -> std::same_as;
问题根源:C++模板的两阶段查找规则
模板的名字查找分为两个阶段:
- 模板定义阶段:查找非依赖名称,以及对依赖名称进行初步语法检查。
- 模板实例化阶段:查找依赖名称,包括通过**参数依赖查找(ADL)**找到的类型关联命名空间中的名字。
对于你的代码:
baz::bar是自定义类型,ADL会自动查找其所在的baz命名空间,因此即使my_free_function定义在模板之后,也能被找到,调用正常。unsigned long是内置类型,没有关联命名空间,ADL无法生效;caz::NativeU64是unsigned long的别名,关联命名空间与原类型一致(为空),因此caz命名空间中的my_free_function无法通过ADL被找到。这两个类型的my_free_function在模板定义阶段不可见,实例化时也无法通过ADL找到,导致编译错误。
可行解决方案
方案1:提前声明函数重载(无需提前定义)
你不需要提前实现my_free_function,只需要在Concept和算法模板之前声明所需的重载即可。模板定义阶段只需看到函数声明,后续再提供定义:
// 提前声明需要的函数重载 unsigned long my_free_function(unsigned long); namespace caz { using NativeU64 = unsigned long; NativeU64 my_free_function(NativeU64); } // 引入Concept和算法模板 #include <concepts> template<class T> concept MyConcept = requires (T a) { { my_free_function(a) } -> std::same_as<T>; }; template<MyConcept T> auto algo(T& a) { return my_free_function(a); } // 后续实现函数 namespace baz { struct bar {}; bar my_free_function(bar) { return bar{}; } } unsigned long my_free_function(unsigned long) { return 42; } namespace caz { NativeU64 my_free_function(NativeU64) { return NativeU64{42ul}; } }
方案2:将别名类型改为自定义包装类型(适配ADL)
如果可以修改caz::NativeU64的定义,将其改为真正的自定义结构体而非别名,ADL会自动查找caz命名空间中的my_free_function,无需提前声明:
#include <concepts> // 引入Concept和算法模板 template<class T> concept MyConcept = requires (T a) { { my_free_function(a) } -> std::same_as<T>; }; template<MyConcept T> auto algo(T& a) { return my_free_function(a); } namespace baz { struct bar {}; bar my_free_function(bar) { return bar{}; } } unsigned long my_free_function(unsigned long) { return 42; } namespace caz { // 改为自定义包装类型 struct NativeU64 { unsigned long value; }; // 定义对应my_free_function NativeU64 my_free_function(NativeU64) { return {42ul}; } }
此时algo(c)调用时,ADL会自动查找caz命名空间,找到对应的函数。
方案3:调整Concept的查找逻辑(依赖ADL的通用设计)
如果你希望所有类型都通过ADL查找函数,可以调整Concept和算法的实现,确保my_free_function的查找优先使用ADL:
#include <concepts> #include <utility> namespace adl_support { // 空函数模板,用于触发ADL查找 template<class T> auto my_free_function(T&&) = delete; } template<class T> concept MyConcept = requires (T a) { // 引入ADL查找,同时允许全局命名空间的函数 using adl_support::my_free_function; { my_free_function(std::forward<T>(a)) } -> std::same_as<T>; }; template<MyConcept T> auto algo(T& a) { using adl_support::my_free_function; return my_free_function(a); }
对于自定义类型,ADL会找到其命名空间中的my_free_function;对于内置类型,需要确保全局命名空间的my_free_function在实例化前已声明或定义。
内容的提问来源于stack exchange,提问作者RandomBits

