C++26 reflection 按名称检测成员函数时,为什么 concept 断言失败?
结论:比较前需要去掉类型别名
比较失败的原因出在这一行:
std::meta::type_of(mem) == ^^Sig
Sig 是通过 using 声明的类型别名。^^Sig 表示别名本身的反射,std::meta::type_of(mem) 返回的则是成员函数类型的反射。即使别名指向的类型相同,这两个反射值也不因此相等。
原有按名称遍历的思路可以保留,只需在比较时对右侧调用 std::meta::dealias:
if (std::meta::type_of(mem) == std::meta::dealias(^^Sig))
return true;
P2996R13 的 Handling Aliases 说明了这一差别:反射可以区分别名与其底层类型,dealias 用于取得底层实体的反射。
为什么类型相同,反射却不相等?
在普通 C++ 类型判断中,类型别名不会创建新类型;reflection 则可以保留“这是哪个别名”的信息。例如:
using Number = int;
static_assert(^^Number != ^^int);
static_assert(std::meta::dealias(^^Number) == ^^int);
你的代码中也是如此:
using Sig = out(in...);
当 out 为 int、in... 为 double 时,Sig 指向函数类型 int(double),但 ^^Sig 仍然是别名的反射。对于示例中的 int X::foo(double),type_of 查询得到的是函数类型,而非成员函数指针类型,因此无需把目标类型改成 int (X::*)(double)。C++ 工作草案对 type_of 的定义规定,该查询返回被反射函数的类型。
如何修改 concept?
保留原来的 LiteralString 和 operator""_ls,将 concept 替换为下面的实现。目标类型的计算移到了循环外,循环中会检查成员是否有 identifier 以及名称是否匹配:
template <typename T, LiteralString LS, typename out, typename... in>
concept has_member_function_named = []() {
using Sig = out(in...);
constexpr auto expected = std::meta::dealias(^^Sig);
auto members = std::meta::members_of(
^^T, std::meta::access_context::current());
for (auto mem : members) {
if (!std::meta::is_function(mem))
continue;
if (!std::meta::has_identifier(mem))
continue;
if (std::meta::identifier_of(mem) != std::string_view(LS))
continue;
if (std::meta::type_of(mem) == expected)
return true;
}
return false;
}();
struct X {
int foo(double);
};
static_assert(has_member_function_named<X, "foo"_ls, int, double>);
static_assert(!has_member_function_named<X, "bar"_ls, int, double>);
static_assert(!has_member_function_named<X, "foo"_ls, void, double>);
static_assert(!has_member_function_named<X, "foo"_ls, int, int>);
这些断言分别测试名称和函数类型匹配、不存在该名称、返回类型不匹配、参数类型不匹配的情况。它们要求编译器实现示例使用的 C++26 reflection 接口;原问题没有提供具体编译器版本。
原始代码还使用了 std::copy 和 std::size_t,应显式包含相应头文件:
#include <algorithm>
#include <cstddef>
#include <meta>
#include <string_view>
检测范围需要注意什么?
这个实现检查指定名称对应的函数类型是否精确匹配,使用时需要注意:
- 类型匹配不等于可调用性检查。
foo(int)即使能接收可转换的double实参,也不会匹配目标类型int(double)。 - 限定符会影响结果。
const、引用限定符以及noexcept可以形成不同的函数类型。当前out(in...)无法表达这些额外要求;需要检测它们时,可以扩展接口,例如直接接受完整函数类型。 - 当前实现允许 static 成员函数匹配。如果只接受非静态成员函数,可以在循环中增加
if (std::meta::is_static_member(mem)) continue;。 - 成员枚举有范围限制。
members_of枚举直接成员,并按传入的 access context 过滤访问权限,不会自动递归查找基类。这里还要求T是可供查询的完整类类型,不能假定任意输入类型都只会得到 false。成员查询规则
备注:内容仅供参考。
