You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 20:42:26