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

C++可变模板:参数形式转换及Item构造入参适配问题

问题分析与解决方案

1. 修复Item构造函数的约束不满足问题

原问题中构造函数无法推导两个独立可变参数包是核心原因——C++编译器不支持同时推导两个非末尾的可变参数包。解决思路是通过tuple封装参数包或自定义键值对类型来传递参数:

方案A:用tuple传递拆分后的名称/值列表

调整Item构造函数,接受两个tuple分别承载名称和值,避免推导歧义:

#include <vector>
#include <tuple>
#include <any>
#include <string>
#include <utility>
#include <type_traits>

class Item {
private:
    std::vector<std::string> names;
    std::vector<std::any> values;
public:
    // 接受名称tuple和值tuple的构造函数
    template <typename... Names, typename... Values>
    requires (std::convertible_to<std::decay_t<Names>, std::string> && ...)
    Item(std::tuple<Names...> names_tuple, std::tuple<Values...> values_tuple) {
        // 展开名称tuple
        std::apply([this](auto&&... names) {
            (this->names.emplace_back(std::forward<decltype(names)>(names)), ...);
        }, std::move(names_tuple));
        // 展开值tuple
        std::apply([this](auto&&... values) {
            (this->values.emplace_back(std::forward<decltype(values)>(values)), ...);
        }, std::move(values_tuple));
    }
};

方案B:自定义键值对类型(更直观)

先定义语义明确的键值对结构体,让Item直接接受任意数量的键值对:

class Item {
private:
    std::vector<std::string> names;
    std::vector<std::any> values;
public:
    // 自定义键值对类型
    template <typename Name, typename Value>
    struct KeyValue {
        Name name;
        Value value;
    };

    // 推导指引,支持{name, value}直接构造
    template <typename Name, typename Value>
    KeyValue(Name, Value) -> KeyValue<Name, Value>;

    // 接受任意数量KeyValue的构造函数
    template <typename... KVs>
    requires (std::is_same_v<std::decay_t<KVs>, KeyValue<std::decay_t<decltype(std::declval<KVs>().name)>, std::decay_t<decltype(std::declval<KVs>().value)>>> && ...)
    Item(KVs&&... kvs) {
        (names.emplace_back(std::forward<decltype(kvs.name)>(kvs.name)), ...);
        (values.emplace_back(std::forward<decltype(kvs.value)>(kvs.value)), ...);
    }
};

2. 实现collect API并适配Item构造

collect的核心是将成对的(名称-值)参数拆分为两个tuple,再传递给Item构造函数:

// 递归拆分成对参数的辅助函数
auto split_pairs() {
    return std::make_pair(std::tuple<>(), std::tuple<>());
}

template <typename Name, typename Value, typename... Rest>
auto split_pairs(Name&& name, Value&& value, Rest&&... rest) {
    auto [names, values] = split_pairs(std::forward<Rest>(rest)...);
    return std::make_pair(
        std::tuple_cat(std::make_tuple(std::forward<Name>(name)), names),
        std::tuple_cat(std::make_tuple(std::forward<Value>(value)), values)
    );
}

// 对外暴露的collect API
template <typename... Args>
auto collect(Args&&... args) {
    static_assert(sizeof...(Args) % 2 == 0, "Arguments must be in (name, value) pairs");
    return split_pairs(std::forward<Args>(args)...);
}

3. 适配Logger的emplace_back逻辑

在Logger类中实现emplace_item方法,将collect的结果传递给Item构造:

class Logger {
public:
    std::vector<Item> items;

    // 支持成对参数直接传入
    template <typename... Args>
    void emplace_item(Args&&... args) {
        auto [names_tuple, values_tuple] = collect(std::forward<Args>(args)...);
        items.emplace_back(std::move(names_tuple), std::move(values_tuple));
    }

    // 支持KeyValue类型参数传入(可选)
    template <typename... KVs>
    void emplace_item(Item::KeyValue<KVs...>&&... kvs) {
        items.emplace_back(std::forward<Item::KeyValue<KVs...>>(kvs)...);
    }
};

4. 调用示例

int main() {
    Logger logger;
    // 成对参数形式调用
    logger.emplace_item("age", 25, "name", "Alice", "score", 95.5);

    // KeyValue形式调用(可选)
    logger.emplace_item(Item::KeyValue{"height", 170}, Item::KeyValue{"weight", 65.0});
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 09:54:25