当前位置: 首页 > news >正文

C++反射之获取可调用对象的详细信息

目录

1.可调用对象

1.1.可调用对象分类

1.2.可调用对象检测

2.获取可调用对象的详细信息

2.1.获取函数签名信息(返回值类型和参数类型)

2.2.处理 std::function 和通用可调用对象

2.3.运行时反射(有限支持)

2.4.借助 Boost 库的function_traits

2.5.C++20 的std::invocable与概念(Concepts)

3.应用场景

4.总结

推荐阅读


1.可调用对象

1.1.可调用对象分类

        在 C++ 中,可调用对象(Callable Object) 是指可以使用函数调用运算符 () 执行的实体。这是一个广泛的概念,涵盖了多种不同的类型,它们在语法上都可以像函数一样被调用。

1.普通函数
最基本的可调用实体,通过函数名和参数列表调用。

int add(int a, int b) {return a + b;
}// 调用
int result = add(3, 4);  // 7

2.函数指针

指向函数的指针,可以通过解引用调用函数。

int (*func_ptr)(int, int) = &add;  // 或直接写 add(函数名自动转换为指针)// 调用方式1
int result1 = (*func_ptr)(3, 4);  // 7
// 调用方式2(更常见)
int result2 = func_ptr(3, 4);     // 7

3.成员函数指针

指向类成员函数的指针,调用时需要绑定到具体对象。

struct Calculator {int add(int a, int b) const { return a + b; }
};int (Calculator::*mem_func_ptr)(int, int) const = &Calculator::add;// 调用(需通过对象或对象指针)
Calculator calc;
int result = (calc.*mem_func_ptr)(3, 4);  // 通过对象调用
int result2 = (calc_ptr->*mem_func_ptr)(3, 4);  // 通过指针调用

C++反射之检测struct或class是否实现指定函数_c++判断函数是否存在-CSDN博客 

4.仿函数(Functor)

重载了 operator() 的类对象,行为类似函数。

struct Adder {int operator()(int a, int b) const {return a + b;}
};Adder adder;
int result = adder(3, 4);  // 7(调用Adder::operator())

5.Lambda 表达式

C++11 引入的匿名函数对象,本质是编译器自动生成的仿函数。

auto lambda = [](int a, int b) { return a + b; };
int result = lambda(3, 4);  // 7

6.std::function

标准库提供的通用多态函数包装器,可以存储、复制和调用任何可调用对象。

std::function<int(int, int)> func = [](int a, int b) { return a + b; };
int result = func(3, 4);  // 7

7.绑定表达式(std::bind)的结果

通过 std::bind 绑定参数后的可调用对象。

auto add5 = std::bind(add, std::placeholders::_1, 5);
int result = add5(3);  // 等价于 add(3, 5) → 8

1.2.可调用对象检测

使用 std::is_invocable(C++17 起)或手动实现的类型特征来检测一个对象是否可调用:

#include <iostream>
#include <type_traits>// C++17 及以后版本
template <typename F, typename... Args>
constexpr bool is_callable_v = std::is_invocable_v<F, Args...>;// C++11/14 兼容实现
template <typename F, typename... Args>
struct is_callable {
private:template <typename T>static auto test(int) -> decltype(std::declval<T>()(std::declval<Args>()...), std::true_type{});template <typename>static std::false_type test(...);public:static constexpr bool value = decltype(test<F>(0))::value;
};

std::is_invocable为C++17及以后版本支持的,这就不说什么了。看C++11/14的实现利用SFINAE技术来检测是否匹配可调用对象,关于SFINAE技术可参考

C++惯用法: 通过std::decltype来SFINAE掉表达式-CSDN博客

std::declval在这里是获取T的右值类型,如果传入函数,看似是函数调用,实则不会真实调用这个函数,后面就是逗号表达式,很好理解。

C++之std::declval-CSDN博客

示例:

// 示例用法
int main() {auto lambda = [](int x) { return x * 2; };std::cout << std::boolalpha;std::cout << "lambda 可调用: " << is_callable_v<decltype(lambda), int> << "\n";std::cout << "lambda 不可调用: " << is_callable_v<decltype(lambda), double> << "\n";return 0;
}

2.获取可调用对象的详细信息

2.1.获取函数签名信息(返回值类型和参数类型)

核心目标

FunctionTraits 的核心目标是将 “可调用对象的类型” 映射为一组可访问的元信息,通常包括:

  • result_type:返回值类型;
  • arity:参数个数(编译期常量);
  • args:参数类型的元组(如 std::tuple<Arg1, Arg2, ...>);
  • arg<n>_type:第 n 个参数的类型(如 arg0_typearg1_type 等)。

实现原理:模板特化

FunctionTraits 的实现依赖模板特化,针对不同类型的可调用对象(函数指针、成员函数、lambda 等)编写特化版本,提取其 operator() 或函数签名的信息。

1.基础框架与普通函数 / 函数指针

首先处理最直接的情况:普通函数和函数指针(它们的类型签名为 Ret(Args...) 或 Ret(*)(Args...))。

#include <tuple>
#include <cstddef>  // size_t// 主模板(默认情况,需特化匹配具体类型)
template <typename T>
struct FunctionTraits;// 特化:匹配普通函数/函数指针(Ret(Args...) 或 Ret(*)(Args...))
template <typename Ret, typename... Args>
struct FunctionTraits<Ret(Args...)> {using result_type = Ret;                  // 返回值类型static constexpr size_t arity = sizeof...(Args);  // 参数个数using args_tuple = std::tuple<Args...>;   // 参数类型元组// 提取第 n 个参数的类型(n 从 0 开始)template <size_t N>struct arg {static_assert(N < arity, "Parameter index exceeds function arity");using type = typename std::tuple_element<N, args_tuple>::type;};
};// 特化:匹配函数指针(Ret(*)(Args...))
template <typename Ret, typename... Args>
struct FunctionTraits<Ret(*)(Args...)> : FunctionTraits<Ret(Args...)> {};

示例:提取普通函数的信息

int add(int a, double b) { return a + static_cast<int>(b); }// 获取 add 函数的信息
using AddTraits = FunctionTraits<decltype(add)>;
// AddTraits::result_type → int
// AddTraits::arity → 2(参数个数)
// AddTraits::args_tuple → std::tuple<int, double>
// AddTraits::arg<0>::type → int(第一个参数类型)
// AddTraits::arg<1>::type → double(第二个参数类型)

2.处理成员函数

成员函数的类型签名为 Ret(Class::*)(Args...)(非静态成员函数有隐含的 this 指针作为第一个参数),需要单独特化:

// 特化:匹配非静态成员函数(Ret(Class::*)(Args...))
template <typename Class, typename Ret, typename... Args>
struct FunctionTraits<Ret(Class::*)(Args...)> {using result_type = Ret;using class_type = Class;  // 成员函数所属的类static constexpr size_t arity = sizeof...(Args);  // 显式参数个数(不含this)using args_tuple = std::tuple<Args...>;// 提取第 n 个参数的类型(n 从 0 开始)template <size_t N>struct arg {static_assert(N < arity, "Parameter index exceeds function arity");using type = typename std::tuple_element<N, args_tuple>::type;};
};// 特化:匹配 const 非静态成员函数(Ret(Class::*)(Args...) const)
template <typename Class, typename Ret, typename... Args>
struct FunctionTraits<Ret(Class::*)(Args...) const> {using result_type = Ret;using class_type = Class;static constexpr size_t arity = sizeof...(Args);using args_tuple = std::tuple<Args...>;// 提取第 n 个参数的类型(n 从 0 开始)template <size_t N>struct arg {static_assert(N < arity, "Parameter index exceeds function arity");using type = typename std::tuple_element<N, args_tuple>::type;};
};

示例:提取成员函数的信息

struct Calculator {int multiply(int a, int b) const { return a * b; }
};// 获取 multiply 成员函数的信息
using MultiplyTraits = FunctionTraits<decltype(&Calculator::multiply)>;
// MultiplyTraits::result_type → int
// MultiplyTraits::class_type → Calculator(所属类)
// MultiplyTraits::arity → 2(参数个数)
// MultiplyTraits::arg<0>::type → int(第一个参数)

3.处理 lambda 和仿函数

lambda 表达式和仿函数(带 operator() 的类)的类型是匿名 / 自定义类,其核心是 operator() 成员函数。因此,可通过递归解析其 operator() 的类型来提取信息:

// 特化:针对仿函数/lambda(通过其 operator() 提取信息)
template <typename T>
struct FunctionTraits : FunctionTraits<decltype(&T::operator())> {};

原理:lambda 或仿函数的类型 T 包含 operator(),因此 decltype(&T::operator()) 会得到该成员函数的类型,再通过上一步的成员函数特化即可提取信息。

示例:提取 lambda 的信息

auto lambda = [](std::string s, float f) { return s.size() + static_cast<size_t>(f); };// 获取 lambda 的信息
using LambdaTraits = FunctionTraits<decltype(lambda)>;
// LambdaTraits::result_type → size_t
// LambdaTraits::arity → 2(参数个数)
// LambdaTraits::arg<0>::type → std::string
// LambdaTraits::arg<1>::type → float

4.处理 std::function

std::function<Ret(Args...)> 本身包含类型签名,可直接特化提取:

#include <functional>// 特化:匹配 std::function<Ret(Args...)>
template <typename Ret, typename... Args>
struct FunctionTraits<std::function<Ret(Args...)>> : FunctionTraits<Ret(Args...)> {};

示例:提取 std::function 的信息

std::function<bool(int, int)> comparator = [](int a, int b) { return a < b; };using ComparatorTraits = FunctionTraits<decltype(comparator)>;
// ComparatorTraits::result_type → bool
// ComparatorTraits::arity → 2

完整实现:整合所有特化

#include <tuple>
#include <cstddef>
#include <functional>// 主模板
template <typename T>
struct FunctionTraits;// 1. 普通函数/函数指针:Ret(Args...) 或 Ret(*)(Args...)
template <typename Ret, typename... Args>
struct FunctionTraits<Ret(Args...)> {using result_type = Ret;static constexpr size_t arity = sizeof...(Args);using args_tuple = std::tuple<Args...>;template <size_t N>struct arg {static_assert(N < arity, "Parameter index exceeds function arity");using type = typename std::tuple_element<N, args_tuple>::type;};
};//特化:匹配函数指针(Ret(*)(Args...))
template <typename Ret, typename... Args>
struct FunctionTraits<Ret(*)(Args...)> : FunctionTraits<Ret(Args...)> {};// 2. 非静态成员函数(含 const 版本)
template <typename Class, typename Ret, typename... Args>
struct FunctionTraits<Ret(Class::*)(Args...)> {using result_type = Ret;using class_type = Class;static constexpr size_t arity = sizeof...(Args);using args_tuple = std::tuple<Args...>;template <size_t N>struct arg {static_assert(N < arity, "Parameter index exceeds function arity");using type = typename std::tuple_element<N, args_tuple>::type;};
};template <typename Class, typename Ret, typename... Args>
struct FunctionTraits<Ret(Class::*)(Args...) const> {using result_type = Ret;using class_type = Class;static constexpr size_t arity = sizeof...(Args);using args_tuple = std::tuple<Args...>;template <size_t N>struct arg {static_assert(N < arity, "Parameter index exceeds function arity");using type = typename std::tuple_element<N, args_tuple>::type;};
};// 3. 仿函数/lambda(通过 operator() 递归)
template <typename T>
struct FunctionTraits : FunctionTraits<decltype(&T::operator())> {};// 4. std::function
template <typename Ret, typename... Args>
struct FunctionTraits<std::function<Ret(Args...)>> : FunctionTraits<Ret(Args...)> {};

这里利用了SFINAE技术模板萃取特性,还有一个小技巧,可变参数用std::tuple保存了起来,又通过std::tuple_element提取出来。

C++之std::tuple(一) : 使用精讲(全)-CSDN博客

C++之std::tuple(二) : 揭秘底层实现原理_std底层实现-CSDN博客

完整示例:

int add(int a, int b)
{return a + b;
}template <typename F, int N>
void printFunctionParam()
{using Traits = FunctionTraits<F>;constexpr int count = Traits::arity;if constexpr (N < count) {std::cout << "  参数 #" << N << " 类型: "<< typeid(typename Traits::template arg<N>::type).name() << "\n";printFunctionParam<F, N + 1>();}
}// 辅助函数,用于打印函数信息
template <typename F>
void printFunctionInfo(const char* name) {using Traits = FunctionTraits<F>;std::cout << "函数: " << name << "\n";std::cout << "  返回值类型: " << typeid(typename Traits::result_type).name() << "\n";std::cout << "  参数数量: " << Traits::arity << "\n";printFunctionParam<F, 0>();
}// 示例用法
struct Calculator {double multiply(double a, double b) const { return a * b; }
};int main()
{printFunctionInfo<decltype(add)>("add");using MultiplyFunc = double(Calculator::*)(double, double) const;printFunctionInfo<MultiplyFunc>("Calculator::multiply");//auto p = [](int a, int b) { return a * b;}printFunctionInfo<decltype([](int a, int b, int c) { return a * b * c;})>("lambdaFunc");return 0;
}

输出示例(类型名可能因编译器而异):

2.2.处理 std::function 和通用可调用对象

对于 std::function 和通用可调用对象(如 lambda),需要更复杂的模板元编程技术:

#include <iostream>
#include <functional>
#include <type_traits>// 通用可调用对象特征萃取器
template <typename T>
struct CallableTraits : public CallableTraits<decltype(&T::operator())> {};// 处理 lambda 表达式的 operator()
template <typename C, typename Ret, typename... Args>
struct CallableTraits<Ret(C::*)(Args...) const> : public FunctionTraits<Ret(Args...)> {};// 处理 std::function
template <typename Ret, typename... Args>
struct CallableTraits<std::function<Ret(Args...)>> : public FunctionTraits<Ret(Args...)> {};

示例用法:

// 示例用法
int main() {auto lambda = [](int a, double b) { return a + b; };std::function<bool(std::string)> func = [](const std::string& s) { return !s.empty(); };using LambdaTraits = CallableTraits<decltype(lambda)>;using FuncTraits = CallableTraits<decltype(func)>;std::cout << "Lambda 返回值类型: " << typeid(typename LambdaTraits::result_type).name() << "\n";std::cout << "Function 返回值类型: " << typeid(typename FuncTraits::result_type).name() << "\n";return 0;
}

2.3.运行时反射(有限支持)

C++ 没有内置的完整运行时反射机制,但可以通过手动注册信息实现有限的反射:

#include <iostream>
#include <string>
#include <map>
#include <functional>// 函数注册表
struct FunctionRegistry {using FunctionInfo = std::tuple<std::string, std::string>; // 返回类型, 参数列表std::map<std::string, FunctionInfo> functions;void registerFunction(const std::string& name, const std::string& returnType, const std::string& paramList) {functions[name] = {returnType, paramList};}void printFunctionInfo(const std::string& name) const {auto it = functions.find(name);if (it != functions.end()) {std::cout << "函数: " << name << "\n";std::cout << "  返回值类型: " << std::get<0>(it->second) << "\n";std::cout << "  参数: " << std::get<1>(it->second) << "\n";} else {std::cout << "未找到函数: " << name << "\n";}}
};// 全局注册表
FunctionRegistry g_registry;// 示例函数
int add(int a, int b) { return a + b; }// 注册函数信息
struct RegisterFunctions {RegisterFunctions() {g_registry.registerFunction("add", "int", "int a, int b");}
};// 静态初始化器确保注册发生
static RegisterFunctions s_registerFunctions;int main() {g_registry.printFunctionInfo("add");return 0;
}

2.4.借助 Boost 库的function_traits

Boost 库提供了boost::function_traits,可直接萃取多种可调用对象的信息,无需手动实现元编程逻辑:

#include <boost/type_traits/function_traits.hpp>int bar(int, char);
using BarTraits = boost::function_traits<decltype(&bar)>;
using BarRet = BarTraits::result_type;  // int
using BarArg1 = BarTraits::arg1_type;   // int

特点:支持函数、函数指针、成员函数等,功能全面,减少手动编码工作量。

2.5.C++20 的std::invocable与概念(Concepts)

C++20 引入的概念(如std::invocable)可用于检查可调用对象是否能以特定参数调用,但不直接提取类型信息,需结合元编程使用:

#include <concepts>// 检查可调用对象是否能接受int和double参数
template <typename F>
requires std::invocable<F, int, double>
void check_callable(F) {}

适用场景:主要用于编译期校验可调用对象的兼容性,而非提取具体类型。

3.应用场景

FunctionTraits 作为编译期提取可调用对象类型信息的工具,其核心价值在于通过模板元编程获取函数签名(返回类型、参数类型、参数数量等),因此其使用场景主要集中在需要依赖这些类型信息实现通用化、类型安全或自动化处理的场景。

1.通用函数包装器(Function Wrapper)

        实现一个能兼容任意可调用对象(普通函数、lambda、成员函数等)的包装器,需要动态适配不同的函数签名。FunctionTraits 可提取被包装函数的参数类型和返回类型,用于定义包装器的接口或实现参数转发。

        例如,实现一个简化版的 std::function

template <typename F>
struct Wrapper {using Traits = FunctionTraits<F>;using ReturnType = typename Traits::result_type;// 提取参数类型列表(如 Args...)using Args = typename Traits::args;// 基于提取的类型定义调用接口template <typename... Args>ReturnType operator()(Args&&... args) {return func(std::forward<Args>(args)...); // 转发参数}private:F func;
};

2.信号 - 槽(Signal-Slot)机制

        在 GUI 框架(如 Qt)或事件驱动系统中,信号(事件)与槽(回调)需要匹配参数类型才能正确绑定。FunctionTraits 可在编译期提取信号和槽的参数类型,验证两者是否兼容(如参数数量、类型是否匹配),避免运行时绑定错误。

        例如,检查槽函数的参数是否能接收信号的参数:

template <typename Signal, typename Slot>
void connect(Signal sig, Slot slot) {// 提取信号和槽的参数类型列表using SignalArgs = typename FunctionTraits<Signal>::args;using SlotArgs = typename FunctionTraits<Slot>::args;// 编译期断言:参数类型匹配(简化示例)static_assert(std::is_same_v<SignalArgs, SlotArgs>, "信号与槽参数不匹配");// 绑定逻辑...
}

3.反射与元数据生成

        C++ 无原生反射,但 FunctionTraits 可提取函数的 “签名元数据”(如参数类型列表、返回类型),用于模拟反射功能,例如:

  • 生成函数调用的字符串描述(如 int(int, float));
  • 在序列化 / 反序列化中,根据函数参数类型自动解析输入数据(如 RPC 框架中,根据函数签名生成参数的序列化规则);
  • 在 ORM 框架中,提取成员函数(如 User::setName(std::string))的参数类型,映射到数据库字段类型。

4.函数适配与回调注册

        当需要将不同签名的函数统一注册到回调系统(如事件循环、状态机)时,FunctionTraits 可帮助生成适配层,自动转换参数类型或填充默认参数。

        例如,注册回调时,根据函数参数数量自动适配:

// 回调注册表,支持不同参数数量的函数
template <typename... Args>
struct CallbackRegistry {// 存储回调:std::function<void(Args...)>std::vector<std::function<void(Args...)>> callbacks;// 注册任意可调用对象,要求其参数与 Args... 兼容template <typename F>void register(F&& f) {using Traits = FunctionTraits<F>;// 检查参数数量是否匹配static_assert(Traits::arity == sizeof...(Args), "参数数量不匹配");// 检查参数类型是否兼容(简化示例)static_assert(std::is_convertible_v<typename Traits::args, std::tuple<Args...>>, "参数类型不兼容");callbacks.emplace_back(std::forward<F>(f));}
};

5. 成员函数绑定与适配

        非静态成员函数的调用依赖于对象实例(隐含 this 指针作为第一个参数),FunctionTraits 可区分普通函数与成员函数,提取 “实际参数类型”(排除 this 指针),用于绑定对象与成员函数。

        例如,将成员函数转换为可调用的函数对象:

template <typename MemFunc, typename Obj>
auto bind_member(MemFunc func, Obj&& obj) {using Traits = FunctionTraits<MemFunc>;// 成员函数的参数类型为:Obj* (this) + 实际参数 Args...using ActualArgs = typename Traits::args_after_this; // 提取排除 this 的参数类型return [func, obj = std::forward<Obj>(obj)](auto&&... args) {return (obj.*func)(std::forward<decltype(args)>(args)...);};
}

7.编译期条件逻辑与重载选择

基于函数的签名(如参数数量、返回类型)在编译期选择不同的处理逻辑。例如:

  • 若函数返回 void,执行逻辑 A;否则执行逻辑 B;
  • 若函数参数数量为 0,直接调用;否则需要传入参数。

示例:

template <typename F>
void process(F&& func) {using Traits = FunctionTraits<F>;if constexpr (std::is_same_v<typename Traits::return_type, void>) {// 处理返回 void 的函数func();} else {// 处理有返回值的函数auto result = func();log(result);}
}

4.总结

  • 编译时信息:通过模板元编程和类型特征,可以在编译时提取函数签名、参数类型等信息。
  • 运行时信息:C++ 原生支持有限,需手动实现注册表或结合第三方库(如 Boost.TypeIndex)。
  • 实际应用:这些技术常用于框架开发(如依赖注入、序列化)、代码生成工具或调试辅助工具。

总之,FunctionTraits 是 C++ 模板元编程的经典应用,核心是通过模板特化 “拆解” 可调用对象的类型签名,为通用编程提供类型信息支持。

推荐阅读

C++之std::tuple(一) : 使用精讲(全)-CSDN博客

C++之std::declval-CSDN博客

C++惯用法: 通过std::decltype来SFINAE掉表达式-CSDN博客

C++可调用Callable类型的总结_c++ callable-CSDN博客 

C++17之std::invoke: 使用和原理探究(全)-CSDN博客 

http://www.dtcms.com/a/265776.html

相关文章:

  • 《Spring 中上下文传递的那些事儿》Part 2:Web 请求上下文 —— RequestContextHolder 与异步处理
  • 低代码实战训练营教学大纲 (10天)
  • Linux之Socket 编程 UDP
  • 自然光实时渲染~三维场景中的全局光照
  • osg加入实时光照SilverLining 天空和3D 云
  • 租车小程序电动车租赁小程序php方案
  • Flutter 3.29+使用isar构建失败
  • 创客匠人视角:知识变现与创始人 IP 打造的破局之道
  • centos7源码编译安装python3
  • SSM和SpringBoot框架的关系
  • 关于微前端框架micro,子应用设置--el-primary-color失效的问题
  • FPGA从零到一实现FOC(一)之PWM模块设计
  • 火语言 RPA:突破企业自动化瓶颈,释放数字生产力​
  • Linux基本命令篇 —— zip/unzip命令
  • Apache Commons Pool中的GenericObjectPool详解
  • 华为Freebuds 6i新音效,设置后音质敲好!
  • Nginx安全配置漏洞修复实战指南
  • 百度文心智能体平台x小米应用商店:联手打造行业首个智能体与应用市场跨端分发模式
  • React 强大的表单验证库formik之集成Yup、React Hook Form库
  • 使用 Dockerfile 构建基于 .NET9 的跨平台基础镜像
  • 安卓开机自启动方案
  • Kafka生态整合深度解析:构建现代化数据架构的核心枢纽
  • Sklearn安装使用教程
  • 机器人焊接电源节气阀
  • 工程化实践——标准化Eslint、PrettierTS
  • 读书笔记:《DevOps实践指南》
  • android 网络访问拦截器的编写的几种方式
  • React 学习(3)
  • springboot 中使用 websocket
  • PHP:从入门到实践——构建高效Web应用的利器