如何实现可聚合初始化的可变参数模板存储不可移动类型?
存储不可移动/不可拷贝类型的可变参数数据结构方案
你遇到的核心问题是std::make_tuple会尝试将传入的临时对象移动或拷贝到tuple成员中,但你的No类型删除了拷贝和移动构造函数,导致操作失败。而手写的Handmade能正常初始化,是因为聚合初始化会直接在结构体的内存空间中原地构造每个成员,完全不需要移动或拷贝操作。
针对可变参数的需求,不同C++标准下有不同的可行方案:
方案一:C++17及以上——直接用std::tuple聚合初始化
C++17标准开始,std::tuple被重新定义为聚合类型,支持直接聚合初始化。这意味着你可以绕过std::make_tuple,直接指定tuple的类型并初始化,这样就能原地构造所有不可移动/不可拷贝的成员:
#include <tuple> template<typename T> struct No { No(T){} No(const No &) = delete; No(No &&) = delete; }; template<typename T> auto no() -> No<T> { return No<T>(T()); } auto main() -> int { // 直接构造tuple并聚合初始化,无需移动/拷贝 auto tuple = std::tuple<No<int>, No<double>, No<char>>{no<int>(), no<double>(), no<char>()}; return 0; }
这种方式完全利用了C++17的聚合初始化特性,不需要额外实现自定义结构。
方案二:C11/C14——自定义可变参数聚合结构体
如果需要兼容更早的C++标准,可以手动实现一个支持聚合初始化的可变参数结构体模板,通过递归继承来容纳不同类型的成员:
1. 定义可变参数结构体
#include <type_traits> template<typename... Ts> struct MyTuple; // 空tuple的特化 template<> struct MyTuple<> {}; // 递归继承:每个特化包含当前类型的成员,并继承剩余类型的MyTuple template<typename T, typename... Ts> struct MyTuple<T, Ts...> : MyTuple<Ts...> { T value; };
2. 实现成员访问的get函数
为了方便访问指定位置的成员,需要实现一个类似std::get的工具函数:
// 非const版本 template<std::size_t I, typename T, typename... Ts> typename std::enable_if<I == 0, T&>::type get(MyTuple<T, Ts...>& t) { return t.value; } template<std::size_t I, typename T, typename... Ts> typename std::enable_if<I != 0, typename std::tuple_element<I-1, MyTuple<Ts...>>::type&>::type get(MyTuple<T, Ts...>& t) { return get<I-1>(static_cast<MyTuple<Ts...>&>(t)); } // const版本 template<std::size_t I, typename T, typename... Ts> typename std::enable_if<I == 0, const T&>::type get(const MyTuple<T, Ts...>& t) { return t.value; } template<std::size_t I, typename T, typename... Ts> typename std::enable_if<I != 0, const typename std::tuple_element<I-1, MyTuple<Ts...>>::type&>::type get(const MyTuple<T, Ts...>& t) { return get<I-1>(static_cast<const MyTuple<Ts...>&>(t)); }
3. 使用示例
template<typename T> struct No { No(T){} No(const No &) = delete; No(No &&) = delete; }; template<typename T> auto no() -> No<T> { return No<T>(T()); } auto main() -> int { MyTuple<No<int>, No<double>, No<char>> my_tuple = {no<int>(), no<double>(), no<char>()}; // 访问第一个成员 auto& int_member = get<0>(my_tuple); return 0; }
这个自定义的MyTuple支持聚合初始化,所有成员都会原地构造,完全不需要移动或拷贝操作,完美适配你的No类型。
内容的提问来源于stack exchange,提问作者slyx
相关产品推荐
相关产品推荐

