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
相关产品推荐
相关产品推荐

