C++20中推导函数首个参数类型并简化RAII类模板参数
问题1:获取未知类型可调用对象的首个参数类型
要提取未知类型可调用对象(比如函数指针、无捕获lambda)的首个参数类型,用C++20的模板特化就能实现。先定义一个类型萃取模板:
#include <type_traits> // 基础模板,匹配任意可调用对象 template<typename Callable> struct first_arg_type; // 特化:匹配普通函数指针 template<typename Ret, typename FirstArg, typename... Rest> struct first_arg_type<Ret(*)(FirstArg, Rest...)> { using type = FirstArg; }; // 特化:匹配无捕获lambda(可隐式转换为函数指针) template<typename Ret, typename FirstArg, typename... Rest> struct first_arg_type<Ret(FirstArg, Rest...)> { using type = FirstArg; }; // 别名简化调用 template<typename Callable> using first_arg_type_t = typename first_arg_type<Callable>::type;
然后在你的模板函数中使用该萃取:
template<auto Callable> void callFunction() { using ArgType = first_arg_type_t<decltype(Callable)>; static_assert(std::is_same_v<ArgType, int*>, "首个参数必须是int*"); } int main() { callFunction<[](int*) {}>(); // 编译通过,ArgType即为int* return 0; }
如果需要支持带捕获的lambda,可额外特化匹配lambda的operator(),但你的示例中是无捕获lambda,上述代码已足够。
背景需求:简化RAII类模板参数
要实现从销毁函数自动推导DataType,同时避免存储函数指针(不增加类占用空间),提供两种实现方案:
方式一:仅传递销毁函数作为模板参数
利用C++20的auto非类型模板参数+模板特化,让raii_t自动从销毁函数签名中推导DataType:
#include <utility> #include <type_traits> // 基础模板声明 template<void (*DestoryFunc)(auto*)> class raii_t; // 特化版本:从销毁函数签名推导DataType template<typename DataType, void (*DestoryFunc)(DataType*)> class raii_t<DestoryFunc> { private: DataType* m_data; public: explicit raii_t(DataType* data) noexcept : m_data(data) {} raii_t(const raii_t&) = delete; auto operator=(const raii_t&) -> raii_t& = delete; raii_t(raii_t&& other) noexcept : m_data(std::exchange(other.m_data, nullptr)) {} auto operator=(raii_t&& other) noexcept -> raii_t& { if (m_data) { DestoryFunc(m_data); } m_data = std::exchange(other.m_data, nullptr); return *this; } ~raii_t() { if (m_data) { DestoryFunc(m_data); } } auto get() noexcept { return m_data; } auto release() noexcept { return std::exchange(m_data, nullptr); } auto reset(DataType* data) { if (m_data) DestoryFunc(m_data); m_data = data; } }; // 推导指南,优化编译器类型推导体验 template<void (*DestoryFunc)(auto*)> raii_t(auto*) -> raii_t<DestoryFunc>; // 测试用销毁函数 void destory(int* ptr) { delete ptr; } int main() { // 你期望的用法:仅传递销毁函数模板参数 auto raii = raii_t<destory>(new int(42)); static_assert(std::is_same_v<decltype(raii.get()), int*>, "DataType应为int"); return 0; }
此方案完美实现raii_t<destory>(nullptr)的写法,无需手动指定DataType。
方式二:构造函数传递销毁函数(但不存储)
若想要类似raii_t(nullptr, destory)的写法,需借助辅助工厂函数——因为销毁函数必须是编译期常量才能作为非类型模板参数,无法直接通过构造函数推导:
#include <utility> #include <type_traits> // 复用方式一的raii_t定义 template<void (*DestoryFunc)(auto*)> class raii_t; template<typename DataType, void (*DestoryFunc)(DataType*)> class raii_t<DestoryFunc> { DataType* m_data; public: explicit raii_t(DataType* data) noexcept : m_data(data) {} raii_t(const raii_t&) = delete; auto operator=(const raii_t&) -> raii_t& = delete; raii_t(raii_t&& other) noexcept : m_data(std::exchange(other.m_data, nullptr)) {} auto operator=(raii_t&& other) noexcept -> raii_t& { if (m_data) DestoryFunc(m_data); m_data = std::exchange(other.m_data, nullptr); return *this; } ~raii_t() { if (m_data) DestoryFunc(m_data); } auto get() noexcept { return m_data; } auto release() noexcept { return std::exchange(m_data, nullptr); } auto reset(DataType* data) { if (m_data) DestoryFunc(m_data); m_data = data; } }; // 辅助工厂函数,支持两种调用方式 template<typename DataType, void (*DestoryFunc)(DataType*)> constexpr auto make_raii(DataType* data) { return raii_t<DestoryFunc>(data); } template<typename DataType, void (*DestoryFunc)(DataType*)> constexpr auto make_raii(DataType* data, decltype(DestoryFunc)) { return raii_t<DestoryFunc>(data); } // 测试用销毁函数 void destory(int* ptr) { delete ptr; } int main() { // 类似构造函数传参的写法 auto raii = make_raii(new int(42), destory); // 明确指定类型的写法 auto raii2 = make_raii<int, destory>(new int(100)); return 0; }
注意:如果销毁函数不是编译期常量(比如动态获取的函数指针),则无法避免存储它——因为非类型模板参数要求编译期已知。但你的需求是不存储函数指针,因此该方案仅适用于编译期常量的销毁函数。
内容的提问来源于stack exchange,提问作者microlife
相关产品推荐
相关产品推荐

