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

能否仅靠C++语言特性自动生成函数的重载包装类?

借助C++特性自动生成包装类的方案

完全可以仅用C++17及以后的语言特性自动生成这类包含多重重载的包装类,无需手动编写所有重载逻辑。下面分两种场景给出具体实现思路:

一、所有参数均为std::optional的情况

针对原函数参数全是std::optional<T>的场景,可以通过可变参数模板、折叠表达式、std::apply和索引序列等特性,自动生成支持任意参数子集的包装方法。

实现代码

#include <optional>
#include <tuple>
#include <type_traits>
#include <utility>

// 判断是否为std::optional类型的辅助模板
template <typename T>
struct is_optional : std::false_type {};

template <typename T>
struct is_optional<std::optional<T>> : std::true_type {
    using value_type = T;
};

// 包装类模板,接受原函数指针作为模板参数
template <auto Func>
class WrapFunc;

// 特化:针对参数全为std::optional的函数
template <typename... Args, void(*Func)(std::optional<Args>...)>
class WrapFunc<Func> {
private:
    // 辅助函数:构造原函数所需的std::optional参数列表
    template <typename Tuple, std::size_t... Idxs, typename... Ts>
    static auto make_args_tuple(std::index_sequence<Idxs...>, const Tuple& args_tuple, Ts...) {
        return std::make_tuple(
            [&]() -> std::optional<Args> {
                using ArgType = Args;
                if constexpr (sizeof...(Ts) == 0) {
                    return std::nullopt;
                } else {
                    // 判断当前参数类型是否在传入的参数列表中
                    constexpr bool is_present = (std::is_same_v<ArgType, Ts> || ...);
                    if constexpr (is_present) {
                        // 找到该类型在传入参数中的位置
                        constexpr auto pos = []() {
                            std::size_t idx = 0;
                            ((std::is_same_v<ArgType, Ts> ? void() : ++idx), ...);
                            return idx;
                        }();
                        return std::make_optional(std::get<pos>(args_tuple));
                    } else {
                        return std::nullopt;
                    }
                }
            }()...
        );
    }

public:
    // 无参数重载
    void wf() {
        auto func_args = make_args_tuple(std::index_sequence_for<Args...>{}, std::tuple<>(), std::tuple<>());
        std::apply(Func, func_args);
    }

    // 任意数量参数的重载
    template <typename... Ts, std::enable_if_t<(sizeof...(Ts) > 0), int> = 0>
    void wf(Ts... args) {
        // 静态断言:传入参数必须匹配原函数可选参数的value_type
        static_assert(((std::disjunction_v<std::is_same<Ts, Args>...>) && ...), 
                      "传入参数类型必须与原函数可选参数的value_type匹配");
        // 静态断言:每个参数类型必须唯一对应原函数的一个可选参数
        static_assert(([]<typename T>(T) {
            return std::count_if(std::initializer_list<bool>{std::is_same_v<T, Args>...}) == 1;
        }(std::declval<Ts>()) && ...), 
                      "每个参数类型必须唯一对应原函数的一个可选参数");
        
        auto args_tuple = std::make_tuple(args...);
        auto func_args = make_args_tuple(std::index_sequence_for<Args...>{}, args_tuple, Ts...);
        std::apply(Func, func_args);
    }
};

// 测试用原函数
void f(std::optional<int> i, std::optional<double> j) {
    // 业务逻辑
}

// 生成包装类
using WrapF = WrapFunc<&f>;

// 使用示例
int main() {
    WrapF wrap;
    wrap.wf();                  // 对应f(nullopt, nullopt)
    wrap.wf(123);               // 对应f(123, nullopt)
    wrap.wf(45.67);             // 对应f(nullopt, 45.67)
    wrap.wf(123, 45.67);        // 对应f(123, 45.67)
    return 0;
}

二、混合必选参数与可选参数的情况

如果原函数同时包含必选参数(非std::optional类型)和可选参数,只需先通过模板元编程分离两类参数,再让包装方法先接受所有必选参数,后续接受任意子集的可选参数。

实现代码

#include <optional>
#include <tuple>
#include <type_traits>
#include <utility>

// 判断是否为std::optional类型的辅助模板
template <typename T>
struct is_optional : std::false_type {};

template <typename T>
struct is_optional<std::optional<T>> : std::true_type {
    using value_type = T;
};

// 分离必选/可选参数的辅助元函数
template <typename... Params>
struct SplitParams {
private:
    // 过滤必选参数(非std::optional)
    template <typename Tuple>
    struct FilterMandatory;

    template <typename... Ts>
    struct FilterMandatory<std::tuple<Ts...>> {
        using type = std::tuple<typename std::enable_if<!is_optional<Ts>::value, Ts>::type...>;
    };

    // 提取可选参数的value_type
    template <typename Tuple>
    struct ExtractOptional;

    template <typename... Ts>
    struct ExtractOptional<std::tuple<Ts...>> {
        using type = std::tuple<typename std::enable_if<is_optional<Ts>::value, typename is_optional<Ts>::value_type>::type...>;
    };

public:
    using Mandatory = typename FilterMandatory<std::tuple<Params...>>::type;
    using Optional = typename ExtractOptional<std::tuple<Params...>>::type;
};

// 包装类模板
template <auto Func>
class WrapFunc;

// 特化:针对混合必选、可选参数的函数
template <typename... Params, void(*Func)(Params...)>
class WrapFunc<Func> {
private:
    // 从SplitParams中获取必选、可选参数类型
    using MandatoryTuple = typename SplitParams<Params...>::Mandatory;
    using OptionalTuple = typename SplitParams<Params...>::Optional;

    // 从元组中提取类型序列
    template <typename Tuple>
    struct TupleToTypes;

    template <typename... Ts>
    struct TupleToTypes<std::tuple<Ts...>> {
        using types = std::tuple<Ts...>;
    };

    using Mandatory = typename TupleToTypes<MandatoryTuple>::types;
    using Optional = typename TupleToTypes<OptionalTuple>::types;

    // 辅助函数:构造原函数所需的可选参数列表
    template <typename Tuple, std::size_t... Idxs, typename... Ts>
    static auto make_optional_args(std::index_sequence<Idxs...>, const Tuple& args_tuple, Ts...) {
        return std::make_tuple(
            [&]() -> std::optional<std::tuple_element_t<Idxs, OptionalTuple>> {
                using OptType = std::tuple_element_t<Idxs, OptionalTuple>;
                constexpr bool is_present = (std::is_same_v<OptType, Ts> || ...);
                if constexpr (is_present) {
                    constexpr auto pos = []() {
                        std::size_t idx = 0;
                        ((std::is_same_v<OptType, Ts> ? void() : ++idx), ...);
                        return idx;
                    }();
                    return std::make_optional(std::get<pos>(args_tuple));
                } else {
                    return std::nullopt;
                }
            }()...
        );
    }

public:
    // 展开必选参数类型
    template <typename... MandatoryArgs, std::enable_if_t<std::is_same_v<std::tuple<MandatoryArgs...>, MandatoryTuple>, int> = 0>
    void wf(MandatoryArgs... mandatory_args) {
        // 调用原函数,可选参数全传nullopt
        std::apply([&](auto... opt_args) {
            Func(mandatory_args..., opt_args...);
        }, std::make_tuple(std::nullopt...));
    }

    // 必选参数 + 任意数量可选参数
    template <typename... MandatoryArgs, typename... Ts, 
              std::enable_if_t<std::is_same_v<std::tuple<MandatoryArgs...>, MandatoryTuple>, int> = 0>
    void wf(MandatoryArgs... mandatory_args, Ts... optional_args) {
        // 静态断言:可选参数类型必须匹配原函数的可选参数value_type
        static_assert(((std::disjunction_v<std::is_same<Ts, std::tuple_element_t<Idxs, OptionalTuple>> || ...>) && ...), 
                      "可选参数类型必须与原函数可选参数的value_type匹配");
        // 静态断言:每个可选参数类型唯一对应一个原函数可选参数
        static_assert(([]<typename T>(T) {
            return std::count_if(std::initializer_list<bool>{std::is_same_v<T, std::tuple_element_t<Idxs, OptionalTuple>> || ...}) == 1;
        }(std::declval<Ts>()) && ...), 
                      "每个可选参数类型必须唯一对应原函数的一个可选参数");
        
        auto optional_tuple = std::make_tuple(optional_args...);
        auto func_optional_args = make_optional_args(std::make_index_sequence<std::tuple_size_v<OptionalTuple>>{}, 
                                                     optional_tuple, Ts...);
        std::apply([&](auto... opt_args) {
            Func(mandatory_args..., opt_args...);
        }, func_optional_args);
    }
};

// 测试用原函数(混合必选int,可选double、int)
void f(int a, std::optional<double> b, std::optional<int> c) {
    // 业务逻辑
}

// 生成包装类
using WrapF = WrapFunc<&f>;

// 使用示例
int main() {
    WrapF wrap;
    wrap.wf(123);                  // 对应f(123, nullopt, nullopt)
    wrap.wf(123, 45.67);           // 对应f(123, 45.67, nullopt)
    wrap.wf(123, 789);             // 对应f(123, nullopt, 789)
    wrap.wf(123, 45.67, 789);      // 对应f(123, 45.67, 789)
    return 0;
}

注意事项

  • 上述实现依赖C17特性(折叠表达式、std::apply、std::index_sequence等),如果使用更早的C版本,需要做兼容调整。
  • 如果原函数存在多个可选参数的value_type相同的情况(比如两个std::optional<int>),需要额外引入标签类来区分不同位置的参数,否则无法通过类型推导确定参数对应的位置。

内容的提问来源于stack exchange,提问作者perreal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 16:52:03