相关系列文章C之std::tuple(二) : 揭秘底层实现原理C三剑客之std::any(一) : 使用C之std::tuple(一) : 使用精讲(全)C三剑客之std::variant(一) : 使用C三剑客之std::variant(二)深入剖析深入理解可变参数(va_list、std::initializer_list和可变参数模版)std::apply源码分析目录1.简介2.std::ignore介绍3.创建元组3.1.直接初始化方式3.2.使用花括号初始化列表方式C11及以上版本3.3.make_tuple方式3.4.使用std::tie()函数方式4.元素访问4.1.std::get ()方式4.2.使用结构化绑定C17及以上4.3.递归遍历元素4.4.std::apply方式(C17及以上)5.获取std::tuple的size6.获取元组中的元素类型7.std::forward_as_tuple7.1.std::forward_as_tuple和std::make_tuple区别8.std::tuple_cat9.std::swap10.std::make_from_tuple11.项目实战11.1.std::tuple的序列化和反序列化11.2.获取std::tuple的数据大小12.总结1.简介C11之后引入了std::tuple俗称元组元组(tuple)是一种用于组合多个不同类型的值的数据结构。元组可以将不同类型的数据打包在一起类似于一个容器可以按照索引顺序访问其中的元素。元组的大小在编译时确定不支持动态添加或移除元素。std::tuple的定义如下templateclass... Types class tuple;std::tuple类似互C语言的结构体不需要创建结构体而又有结构体的特征在某些情况下可以取代结构体而使得程序更加简洁直观。std::tuple理论上可以定义无数多个不同类型的成员变量。特别是你需要在函数之间返回多个值时或者需要一次性处理多个相关值时使用元组可以简化代码并提高可读性。2.std::ignore介绍在标头tuple定义任何值均可赋给而无效果的未指定类型的对象。目的是令 std::tie 在解包 std::tuple 时作为不使用的参数的占位符使用。例如解包 set.insert() 所返回的 pair 但只保存布尔值。#include iostream #include string #include set #include tuple int main() { std::setstd::string set_of_str; bool inserted false; std::tie(std::ignore, inserted) set_of_str.insert(Test); if (inserted) { std::cout Value was inserted successfully\n; } }输出Value was inserted successfully3.创建元组3.1.直接初始化方式//显示初始化 std::tuplebool, int, double, std::string a(true, 1, 3.0, 1112222);3.2.使用花括号初始化列表方式C11及以上版本//显示初始化 std::tuplebool, int, double, std::string a{true, 1, 3.0, 1112222};3.3.make_tuple方式//显示初始化 std::tuplebool, int, double, std::string a make_tuple(true, 1, 3.0, 1112222); //隐式初始化 auto b make_tuple(true, 1, 3.0, 1112222);3.4.使用std::tie()函数方式std::tie定义为templateclass... Types constexpr tupleTypes... tie (Types... args) noexcept;std::tie生成一个tuple,此tuple包含的分量全部为实参的引用与make_tuple完全相反。主要用于从tuple中提取数据。例如bool myBool; int myInt; double myDouble; std::string myString; std::tie(myBool, myInt, myDouble, myString) std::make_tuple(true, 1, 3.0, 1112222);如果是要忽略某个特定的元素还可以使用第2章节的std::ignore来占位例如bool myBool; std::string myString; std::tie(myBool, std::ignore, std::ignore, myString) std::make_tuple(true, 1, 3.0, 1112222);4.元素访问4.1.std::getindex()方式使用std::get来访问std::tuple特定的元素如std::tuplebool, int, std::string a(true, 0, sfsfs); bool b std::get0(a); int c std::get1(a); std::string d std::get2(a); std::get0(a) false; std::get2(a) s344242;4.2.使用结构化绑定C17及以上在C17及以上版本中还可以使用结构化绑定 (structured bindings)的方式来创建和访问元组可以更方便地访问和操作元组中的元素。结构化绑定允许直接从元组中提取元素并赋值给相应的变量。例如std::tuplebool, int, std::string myTuple(true, false, Hello); auto [a, b, c] myTuple;这将自动创建变量a、b和c并将元组中相应位置的值赋给它们。注意元组是不可变的immutable一旦创建就不能更改其元素的值。但是可以通过解构赋值或使用std::getindex(tuple)来获取元组中的值并将新的值赋给它们从而修改元组中的值。std::tuple不支持迭代器获取元素的值时只能通过元素索引或tie解包。给定的索引必须是在编译期间就已经确定的不能在运行期间动态传递否则会产生编译错误4.3.递归遍历元素由于 tuple 自身的原因无法直接遍历而 getindex 中 index 必须为运行前设置好的常数所以 tuple 的遍历需要我们手写代码如下:templateclass Tuple, std::size_t N struct VisitTuple { static void Visit(const Tuple value) { VisitTupleTuple, N - 1::Visit(value); std::cout std::getN - 1(value); return void(); } }; templateclass Tuple struct VisitTupleTuple, 1 { static void Visit(const Tuple value) { std::cout std::get0(value); return void(); } }; templateclass... Args void TupleVisit(const std::tupleArgs... value) { VisitTupledecltype(value), sizeof ...(Args)::Visit(value); }4.4.std::apply方式(C17及以上)利用可变参数的折叠表达式规则来访问std::tuple的元素例如#include iostream #include tuple #include utility int add(int first, int second) { return first second; } templatetypename T T add_generic(T first, T second) { return first second; } auto add_lambda [](auto first, auto second) { return first second; }; templatetypename... Ts std::ostream operator(std::ostream os, std::tupleTs... const theTuple) { std::apply ( [os](Ts const... tupleArgs) { os [; std::size_t n{0}; ((os tupleArgs (n ! sizeof...(Ts) ? , : )), ...); os ]; }, theTuple ); return os; } int main() { // OK std::cout std::apply(add, std::pair(1, 2)) \n; // 错误无法推导函数类型 // std::cout std::apply(add_generic, std::make_pair(2.0f, 3.0f)) \n; // OK std::cout std::apply(add_lambda, std::pair(2.0f, 3.0f)) \n; // 进阶示例 std::tuple myTuple(25, Hello, 9.31f, c); std::cout myTuple \n; }输出3 5 [25, Hello, 9.31, c]上面语句((os tupleArgs (n ! sizeof...(Ts) ? , : )), ...);利用了C17的折叠表达式折叠表达式是C17新引进的语法特性。使用折叠表达式可以简化对C11中引入的参数包的处理从而在某些情况下避免使用递归。如果有不是很明白的地方可参考我的博客深入理解可变参数(va_list、std::initializer_list和可变参数模版)-CSDN博客关于std::applay的使用有不明白的地方可以参考我的博客std::apply源码分析-CSDN博客5.获取std::tuple的sizestd::tuple_size的定义如下template class... Types struct tuple_size std::tupleTypes... : std::integral_constantstd::size_t, sizeof...(Types) { };提供对 tuple 中元素数量的访问作为编译时常量表达式计算std::tuple的大小。例如#include iostream #include tuple template class T void test(T value) { int a[std::tuple_size_vT]; // 能用于编译时 std::cout std::tuple_sizeT{} // 或运行时 sizeof a sizeof value \n; } int main() { test(std::make_tuple(1, 2, 3.14)); }可能的输出3 12 166.获取元组中的元素类型std::tuple_element定义如下template std::size_t I, class... Types class tuple_element I, tupleTypes... ;可以使用std::tuple_elementindex, tuple::type来获取元组中特定索引位置的元素类型。#include iostream #include tuple template class... Args struct type_list { template std::size_t N using type typename std::tuple_elementN, std::tupleArgs...::type; }; int main() { std::cout std::boolalpha; type_listint, char, bool::type2 x true; std::cout x \n; }输出true7.std::forward_as_tuple定义如下template class... Types tupleTypes... forward_as_tuple( Types... args ) noexcept; template class... Types constexpr tupleTypes... forward_as_tuple( Types... args ) noexcept;用于接受右值引用数据生成tuple, 与std::make_tuple不同的是它的右值是引用的当修改其值的时候原来赋值所用的右值也将修改实质上就是赋予了它地址。同std::tie一样也是生成一个全是引用的tuple,不过std::tie只接受左值而std::forward_as_tuple左值、右值都接受。主要是用于不损失类型属性的转发数据。注意此处tuple内的类型应为引用否则相当于std::make_tuple。例如signed main(int argc, char *argv[]) { int a 123, c 456; float b 33.f, d .155; std::tupleint, float, int, float tu std::forward_as_tuple(a,b,c,d); std::get0 (tu) 2; std::get1 (tu) 4.5f; std::get2 (tu) 234; std::get3 (tu) 22.f; std::cout a std::endl; // 2 std::cout b std::endl; // 4.5 std::cout c std::endl; // 234 std::cout d std::endl; // 22 return 0; }注意若参数是临时量则forward_as_tuple不延续其生存期必须在完整表达式结尾前使用它们。7.1.std::forward_as_tuple和std::make_tuple区别std::make_tuple 对参数做类型退化永远生成存储「值」的 tupletupleint, string自动丢弃引用、const、数组 / 函数特性→ 你传引用也没用会被转成值。std::forward_as_tuple不做任何退化完美保留参数的原始类型左值引用 / 右值引用 /const生成存储「引用」的 tupletupleint, int, const string。我们直接看 GCC 标准库的核心源码这是一切区别的根源。1)std::make_tuple源码// 简化版标准库源码 (libstdc) template typename... _Elements constexpr tupletypename decay_Elements::type... // 【关键1返回类型用 decay 退化】 make_tuple(_Elements... __args) // 【转发引用接收参数】 { return tupletypename decay_Elements::type...( std::forward_Elements(__args)... // 【转发参数但类型已被decay改写】 ); }typename decay_Elements::type...这是万恶之源std::decay会强制把类型「退化」为纯值类型int→intint→intconst int→intint[5]→int*函数类型 → 函数指针最终返回的tuple里全是值不可能包含引用。虽然用了转发引用_Elements接收参数但返回类型被decay废掉了引用特性。2)std::forward_as_tuple源码// 简化版标准库源码 (libstdc) template typename... _Elements constexpr tuple_Elements... // 【关键1无decay直接保留原始类型】 forward_as_tuple(_Elements... __args) noexcept // 【转发引用接收参数】 { return tuple_Elements...( std::forward_Elements(__args)... // 【完美转发保留引用】 ); }tuple_Elements...没有decay直接使用参数推导后的原始类型传左值int a→_Elements int→ 返回tupleint传右值10→_Elements int→ 返回tupleint传常量const int a→_Elements const int→ 返回tupleconst intnoexcept仅绑定引用无拷贝 / 移动不抛异常。完美保留参数的值类别左值 / 右值和引用属性。核心区别维度std::make_tuplestd::forward_as_tuple源码核心强制使用std::decay退化类型无decay直接保留原始类型返回类型tupledecay_tT...纯值tupleT...引用 / 转发引用处理丢弃引用转成值保留引用左值→T右值→Tconst 处理丢弃 const转成普通值保留 const →const T内存语义拷贝 / 移动构造值有开销仅绑定引用零拷贝无开销能否修改原变量❌ 不能存的是副本✅ 能左值引用绑定原变量代码实测我们用一段代码直接打印两个函数生成的tuple类型直观看到区别。#include tuple #include iostream #include typeinfo #include cxxabi.h // 打印类型用 // 辅助函数打印C类型名称 std::string type_name(const char* name) { int status; char* demangled abi::__cxa_demangle(name, 0, 0, status); std::string res demangled; free(demangled); return res; } #define PRINT_TYPE(var) \ std::cout #var : type_name(typeid(var).name()) std::endl; int main() { int a 10; const int b 20; // make_tuple全部退化为值 auto t1 std::make_tuple(a); // 传左值int auto t2 std::make_tuple(std::move(a)); // 传右值int auto t3 std::make_tuple(b); // 传const int PRINT_TYPE(t1); // std::tupleint PRINT_TYPE(t2); // std::tupleint PRINT_TYPE(t3); // std::tupleint // forward_as_tuple全部保留引用 auto t4 std::forward_as_tuple(a); // 左值 → int auto t5 std::forward_as_tuple(std::move(a)); // 右值 → int auto t6 std::forward_as_tuple(b); // const左值 → const int PRINT_TYPE(t4); // std::tupleint PRINT_TYPE(t5); // std::tupleint PRINT_TYPE(t6); // std::tupleconst int }输出结果t1 : std::tupleint t2 : std::tupleint t3 : std::tupleint t4 : std::tupleint t5 : std::tupleint t6 : std::tupleconst int8.std::tuple_cat此函数接受多个tuple作为参数然后返回一个tuple。返回的这个tuple将tuple_cat的参数中的tuple的所有元素按所属的tuple在参数中的顺序以及其在tuple中的顺序排列成一个新的tuple。新tuple中元素的类型与参数中的tuple中的元素的类型完全一致。例如#include iostream #include string #include tuple // 打印任何大小 tuple 的辅助函数 templateclass Tuple, std::size_t N struct TuplePrinter { static void print(const Tuple t) { TuplePrinterTuple, N - 1::print(t); std::cout , std::getN-1(t); } }; templateclass Tuple struct TuplePrinterTuple, 1 { static void print(const Tuple t) { std::cout std::get0(t); } }; templateclass... Args void print(const std::tupleArgs... t) { std::cout (; TuplePrinterdecltype(t), sizeof...(Args)::print(t); std::cout )\n; } // 辅助函数结束 int main() { std::tupleint, std::string, float t1(10, Test, 3.14); int n 7; auto t2 std::tuple_cat(t1, std::make_tuple(Foo, bar), t1, std::tie(n)); n 10; print(t2); }输出(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)9.std::swap交换两个std::tuple的内容前提是两个std::tuple的大小和元素类型必须相同例如std::tupleint, double, char a1; std::tupleint, double, char a2; std::tupleunsigned int, double, char a3; std::tupleint, std::string, char a4; std::tupleint, double, char, std::string a5; a1.swap(a2); //OK a2.swap(a3); //编译出现error a3.swap(a4);//编译出现error a4.swap(a5);//编译出现error上面a1和a2的大小和元素类型都相同因此可以交换。a2和a3、a3和a4、a4和a5类型不相同因此不能交换。我们再看一个std::tuple交换的例子#include iostream #include string #include tuple int main() { std::tupleint, std::string, float p1{42, ABCD, 2.71}, p2; p2 std::make_tuple(10, 1234, 3.14); auto print_p1_p2 [](auto rem) { std::cout rem p1 { std::get0(p1) , std::get1(p1) , std::get2(p1) }, p2 { std::get0(p2) , std::get1(p2) , std::get2(p2) }\n; }; print_p1_p2(Before p1.swap(p2): ); p1.swap(p2); print_p1_p2(After p1.swap(p2): ); swap(p1, p2); print_p1_p2(After swap(p1, p2): ); }输出Before p1.swap(p2): p1 {42, ABCD, 2.71}, p2 {10, 1234, 3.14} After p1.swap(p2): p1 {10, 1234, 3.14}, p2 {42, ABCD, 2.71} After swap(p1, p2): p1 {42, ABCD, 2.71}, p2 {10, 1234, 3.14}10.std::make_from_tuplestd::make_from_tuple是以元组std::tuple的元素作为构造函数的参数构造别的类型对象如下例子#include iostream #include tuple struct Foo { Foo(int first, float second, int third) { std::cout first , second , third \n; } }; int main() { auto tuple std::make_tuple(42, 3.14f, 0); std::make_from_tupleFoo(std::move(tuple)); }输出42, 3.14, 011.项目实战11.1.std::tuple的序列化和反序列化利用QDataStream的序列化数据重写操作符operator和operator代码如下templatetypename... Ts QDataStream operator(QDataStream dataStream, std::tupleTs... const theTuple) { std::apply ( [dataStream](Ts const... tupleArgs){ ((dataStream tupleArgs), ...); }, theTuple ); return dataStream; } templatetypename... Ts QDataStream operator(QDataStream dataStream, std::tupleTs... theTuple) { std::apply ( [dataStream](Ts... tupleArgs){ ((dataStream tupleArgs), ...); }, theTuple ); return dataStream; } 或 templatetypename... Ts QDataStream operator(QDataStream dataStream, std::tupleTs... const theTuple) { std::apply ( [dataStream](Ts const... tupleArgs){ (dataStream ... tupleArgs); }, theTuple ); return dataStream; } templatetypename... Ts QDataStream operator(QDataStream dataStream, std::tupleTs... theTuple) { std::apply ( [dataStream](Ts... tupleArgs){ (dataStream ... tupleArgs); }, theTuple ); return dataStream; }11.2.获取std::tuple的数据大小获取std::tuple的实际内容大小但是std::tuple不能包含可变内容长度字段代码如下templateclass Tuple, std::size_t N struct stTupleContentSize{ using first typename std::tuple_elementN-1, Tuple::type; using others stTupleContentSizeTuple, N-1; static constexpr std::size_t sizeof(first) others::size; }; templateclass Tuple struct stTupleContentSizeTuple, 1{ using first typename std::tuple_element0, Tuple::type; static constexpr std::size_t sizeof(first); };调用方法using queryWaveParamData std::tupleint, bool, double, long; constexpr int queryWaveParamDataSize stTupleContentSizequeryWaveParamData, std::tuple_size_vqueryWaveParamData::size;12.总结std::tuple 是一种重要的数据结构可以用于在函数参数之间传递数据也可以作为函数的返回值。在实际项目中我们可以灵活地使用 std::tuple以简化代码提高程序的性能。后面我们将继续通过分析std::tuple源码的方式来更深层次讲解它的实现原理值得期待哦。。。参考std::tuple - cppreference.com
C++之std::tuple(一) : 使用精讲(全)
发布时间:2026/6/5 18:29:19
相关系列文章C之std::tuple(二) : 揭秘底层实现原理C三剑客之std::any(一) : 使用C之std::tuple(一) : 使用精讲(全)C三剑客之std::variant(一) : 使用C三剑客之std::variant(二)深入剖析深入理解可变参数(va_list、std::initializer_list和可变参数模版)std::apply源码分析目录1.简介2.std::ignore介绍3.创建元组3.1.直接初始化方式3.2.使用花括号初始化列表方式C11及以上版本3.3.make_tuple方式3.4.使用std::tie()函数方式4.元素访问4.1.std::get ()方式4.2.使用结构化绑定C17及以上4.3.递归遍历元素4.4.std::apply方式(C17及以上)5.获取std::tuple的size6.获取元组中的元素类型7.std::forward_as_tuple7.1.std::forward_as_tuple和std::make_tuple区别8.std::tuple_cat9.std::swap10.std::make_from_tuple11.项目实战11.1.std::tuple的序列化和反序列化11.2.获取std::tuple的数据大小12.总结1.简介C11之后引入了std::tuple俗称元组元组(tuple)是一种用于组合多个不同类型的值的数据结构。元组可以将不同类型的数据打包在一起类似于一个容器可以按照索引顺序访问其中的元素。元组的大小在编译时确定不支持动态添加或移除元素。std::tuple的定义如下templateclass... Types class tuple;std::tuple类似互C语言的结构体不需要创建结构体而又有结构体的特征在某些情况下可以取代结构体而使得程序更加简洁直观。std::tuple理论上可以定义无数多个不同类型的成员变量。特别是你需要在函数之间返回多个值时或者需要一次性处理多个相关值时使用元组可以简化代码并提高可读性。2.std::ignore介绍在标头tuple定义任何值均可赋给而无效果的未指定类型的对象。目的是令 std::tie 在解包 std::tuple 时作为不使用的参数的占位符使用。例如解包 set.insert() 所返回的 pair 但只保存布尔值。#include iostream #include string #include set #include tuple int main() { std::setstd::string set_of_str; bool inserted false; std::tie(std::ignore, inserted) set_of_str.insert(Test); if (inserted) { std::cout Value was inserted successfully\n; } }输出Value was inserted successfully3.创建元组3.1.直接初始化方式//显示初始化 std::tuplebool, int, double, std::string a(true, 1, 3.0, 1112222);3.2.使用花括号初始化列表方式C11及以上版本//显示初始化 std::tuplebool, int, double, std::string a{true, 1, 3.0, 1112222};3.3.make_tuple方式//显示初始化 std::tuplebool, int, double, std::string a make_tuple(true, 1, 3.0, 1112222); //隐式初始化 auto b make_tuple(true, 1, 3.0, 1112222);3.4.使用std::tie()函数方式std::tie定义为templateclass... Types constexpr tupleTypes... tie (Types... args) noexcept;std::tie生成一个tuple,此tuple包含的分量全部为实参的引用与make_tuple完全相反。主要用于从tuple中提取数据。例如bool myBool; int myInt; double myDouble; std::string myString; std::tie(myBool, myInt, myDouble, myString) std::make_tuple(true, 1, 3.0, 1112222);如果是要忽略某个特定的元素还可以使用第2章节的std::ignore来占位例如bool myBool; std::string myString; std::tie(myBool, std::ignore, std::ignore, myString) std::make_tuple(true, 1, 3.0, 1112222);4.元素访问4.1.std::getindex()方式使用std::get来访问std::tuple特定的元素如std::tuplebool, int, std::string a(true, 0, sfsfs); bool b std::get0(a); int c std::get1(a); std::string d std::get2(a); std::get0(a) false; std::get2(a) s344242;4.2.使用结构化绑定C17及以上在C17及以上版本中还可以使用结构化绑定 (structured bindings)的方式来创建和访问元组可以更方便地访问和操作元组中的元素。结构化绑定允许直接从元组中提取元素并赋值给相应的变量。例如std::tuplebool, int, std::string myTuple(true, false, Hello); auto [a, b, c] myTuple;这将自动创建变量a、b和c并将元组中相应位置的值赋给它们。注意元组是不可变的immutable一旦创建就不能更改其元素的值。但是可以通过解构赋值或使用std::getindex(tuple)来获取元组中的值并将新的值赋给它们从而修改元组中的值。std::tuple不支持迭代器获取元素的值时只能通过元素索引或tie解包。给定的索引必须是在编译期间就已经确定的不能在运行期间动态传递否则会产生编译错误4.3.递归遍历元素由于 tuple 自身的原因无法直接遍历而 getindex 中 index 必须为运行前设置好的常数所以 tuple 的遍历需要我们手写代码如下:templateclass Tuple, std::size_t N struct VisitTuple { static void Visit(const Tuple value) { VisitTupleTuple, N - 1::Visit(value); std::cout std::getN - 1(value); return void(); } }; templateclass Tuple struct VisitTupleTuple, 1 { static void Visit(const Tuple value) { std::cout std::get0(value); return void(); } }; templateclass... Args void TupleVisit(const std::tupleArgs... value) { VisitTupledecltype(value), sizeof ...(Args)::Visit(value); }4.4.std::apply方式(C17及以上)利用可变参数的折叠表达式规则来访问std::tuple的元素例如#include iostream #include tuple #include utility int add(int first, int second) { return first second; } templatetypename T T add_generic(T first, T second) { return first second; } auto add_lambda [](auto first, auto second) { return first second; }; templatetypename... Ts std::ostream operator(std::ostream os, std::tupleTs... const theTuple) { std::apply ( [os](Ts const... tupleArgs) { os [; std::size_t n{0}; ((os tupleArgs (n ! sizeof...(Ts) ? , : )), ...); os ]; }, theTuple ); return os; } int main() { // OK std::cout std::apply(add, std::pair(1, 2)) \n; // 错误无法推导函数类型 // std::cout std::apply(add_generic, std::make_pair(2.0f, 3.0f)) \n; // OK std::cout std::apply(add_lambda, std::pair(2.0f, 3.0f)) \n; // 进阶示例 std::tuple myTuple(25, Hello, 9.31f, c); std::cout myTuple \n; }输出3 5 [25, Hello, 9.31, c]上面语句((os tupleArgs (n ! sizeof...(Ts) ? , : )), ...);利用了C17的折叠表达式折叠表达式是C17新引进的语法特性。使用折叠表达式可以简化对C11中引入的参数包的处理从而在某些情况下避免使用递归。如果有不是很明白的地方可参考我的博客深入理解可变参数(va_list、std::initializer_list和可变参数模版)-CSDN博客关于std::applay的使用有不明白的地方可以参考我的博客std::apply源码分析-CSDN博客5.获取std::tuple的sizestd::tuple_size的定义如下template class... Types struct tuple_size std::tupleTypes... : std::integral_constantstd::size_t, sizeof...(Types) { };提供对 tuple 中元素数量的访问作为编译时常量表达式计算std::tuple的大小。例如#include iostream #include tuple template class T void test(T value) { int a[std::tuple_size_vT]; // 能用于编译时 std::cout std::tuple_sizeT{} // 或运行时 sizeof a sizeof value \n; } int main() { test(std::make_tuple(1, 2, 3.14)); }可能的输出3 12 166.获取元组中的元素类型std::tuple_element定义如下template std::size_t I, class... Types class tuple_element I, tupleTypes... ;可以使用std::tuple_elementindex, tuple::type来获取元组中特定索引位置的元素类型。#include iostream #include tuple template class... Args struct type_list { template std::size_t N using type typename std::tuple_elementN, std::tupleArgs...::type; }; int main() { std::cout std::boolalpha; type_listint, char, bool::type2 x true; std::cout x \n; }输出true7.std::forward_as_tuple定义如下template class... Types tupleTypes... forward_as_tuple( Types... args ) noexcept; template class... Types constexpr tupleTypes... forward_as_tuple( Types... args ) noexcept;用于接受右值引用数据生成tuple, 与std::make_tuple不同的是它的右值是引用的当修改其值的时候原来赋值所用的右值也将修改实质上就是赋予了它地址。同std::tie一样也是生成一个全是引用的tuple,不过std::tie只接受左值而std::forward_as_tuple左值、右值都接受。主要是用于不损失类型属性的转发数据。注意此处tuple内的类型应为引用否则相当于std::make_tuple。例如signed main(int argc, char *argv[]) { int a 123, c 456; float b 33.f, d .155; std::tupleint, float, int, float tu std::forward_as_tuple(a,b,c,d); std::get0 (tu) 2; std::get1 (tu) 4.5f; std::get2 (tu) 234; std::get3 (tu) 22.f; std::cout a std::endl; // 2 std::cout b std::endl; // 4.5 std::cout c std::endl; // 234 std::cout d std::endl; // 22 return 0; }注意若参数是临时量则forward_as_tuple不延续其生存期必须在完整表达式结尾前使用它们。7.1.std::forward_as_tuple和std::make_tuple区别std::make_tuple 对参数做类型退化永远生成存储「值」的 tupletupleint, string自动丢弃引用、const、数组 / 函数特性→ 你传引用也没用会被转成值。std::forward_as_tuple不做任何退化完美保留参数的原始类型左值引用 / 右值引用 /const生成存储「引用」的 tupletupleint, int, const string。我们直接看 GCC 标准库的核心源码这是一切区别的根源。1)std::make_tuple源码// 简化版标准库源码 (libstdc) template typename... _Elements constexpr tupletypename decay_Elements::type... // 【关键1返回类型用 decay 退化】 make_tuple(_Elements... __args) // 【转发引用接收参数】 { return tupletypename decay_Elements::type...( std::forward_Elements(__args)... // 【转发参数但类型已被decay改写】 ); }typename decay_Elements::type...这是万恶之源std::decay会强制把类型「退化」为纯值类型int→intint→intconst int→intint[5]→int*函数类型 → 函数指针最终返回的tuple里全是值不可能包含引用。虽然用了转发引用_Elements接收参数但返回类型被decay废掉了引用特性。2)std::forward_as_tuple源码// 简化版标准库源码 (libstdc) template typename... _Elements constexpr tuple_Elements... // 【关键1无decay直接保留原始类型】 forward_as_tuple(_Elements... __args) noexcept // 【转发引用接收参数】 { return tuple_Elements...( std::forward_Elements(__args)... // 【完美转发保留引用】 ); }tuple_Elements...没有decay直接使用参数推导后的原始类型传左值int a→_Elements int→ 返回tupleint传右值10→_Elements int→ 返回tupleint传常量const int a→_Elements const int→ 返回tupleconst intnoexcept仅绑定引用无拷贝 / 移动不抛异常。完美保留参数的值类别左值 / 右值和引用属性。核心区别维度std::make_tuplestd::forward_as_tuple源码核心强制使用std::decay退化类型无decay直接保留原始类型返回类型tupledecay_tT...纯值tupleT...引用 / 转发引用处理丢弃引用转成值保留引用左值→T右值→Tconst 处理丢弃 const转成普通值保留 const →const T内存语义拷贝 / 移动构造值有开销仅绑定引用零拷贝无开销能否修改原变量❌ 不能存的是副本✅ 能左值引用绑定原变量代码实测我们用一段代码直接打印两个函数生成的tuple类型直观看到区别。#include tuple #include iostream #include typeinfo #include cxxabi.h // 打印类型用 // 辅助函数打印C类型名称 std::string type_name(const char* name) { int status; char* demangled abi::__cxa_demangle(name, 0, 0, status); std::string res demangled; free(demangled); return res; } #define PRINT_TYPE(var) \ std::cout #var : type_name(typeid(var).name()) std::endl; int main() { int a 10; const int b 20; // make_tuple全部退化为值 auto t1 std::make_tuple(a); // 传左值int auto t2 std::make_tuple(std::move(a)); // 传右值int auto t3 std::make_tuple(b); // 传const int PRINT_TYPE(t1); // std::tupleint PRINT_TYPE(t2); // std::tupleint PRINT_TYPE(t3); // std::tupleint // forward_as_tuple全部保留引用 auto t4 std::forward_as_tuple(a); // 左值 → int auto t5 std::forward_as_tuple(std::move(a)); // 右值 → int auto t6 std::forward_as_tuple(b); // const左值 → const int PRINT_TYPE(t4); // std::tupleint PRINT_TYPE(t5); // std::tupleint PRINT_TYPE(t6); // std::tupleconst int }输出结果t1 : std::tupleint t2 : std::tupleint t3 : std::tupleint t4 : std::tupleint t5 : std::tupleint t6 : std::tupleconst int8.std::tuple_cat此函数接受多个tuple作为参数然后返回一个tuple。返回的这个tuple将tuple_cat的参数中的tuple的所有元素按所属的tuple在参数中的顺序以及其在tuple中的顺序排列成一个新的tuple。新tuple中元素的类型与参数中的tuple中的元素的类型完全一致。例如#include iostream #include string #include tuple // 打印任何大小 tuple 的辅助函数 templateclass Tuple, std::size_t N struct TuplePrinter { static void print(const Tuple t) { TuplePrinterTuple, N - 1::print(t); std::cout , std::getN-1(t); } }; templateclass Tuple struct TuplePrinterTuple, 1 { static void print(const Tuple t) { std::cout std::get0(t); } }; templateclass... Args void print(const std::tupleArgs... t) { std::cout (; TuplePrinterdecltype(t), sizeof...(Args)::print(t); std::cout )\n; } // 辅助函数结束 int main() { std::tupleint, std::string, float t1(10, Test, 3.14); int n 7; auto t2 std::tuple_cat(t1, std::make_tuple(Foo, bar), t1, std::tie(n)); n 10; print(t2); }输出(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)9.std::swap交换两个std::tuple的内容前提是两个std::tuple的大小和元素类型必须相同例如std::tupleint, double, char a1; std::tupleint, double, char a2; std::tupleunsigned int, double, char a3; std::tupleint, std::string, char a4; std::tupleint, double, char, std::string a5; a1.swap(a2); //OK a2.swap(a3); //编译出现error a3.swap(a4);//编译出现error a4.swap(a5);//编译出现error上面a1和a2的大小和元素类型都相同因此可以交换。a2和a3、a3和a4、a4和a5类型不相同因此不能交换。我们再看一个std::tuple交换的例子#include iostream #include string #include tuple int main() { std::tupleint, std::string, float p1{42, ABCD, 2.71}, p2; p2 std::make_tuple(10, 1234, 3.14); auto print_p1_p2 [](auto rem) { std::cout rem p1 { std::get0(p1) , std::get1(p1) , std::get2(p1) }, p2 { std::get0(p2) , std::get1(p2) , std::get2(p2) }\n; }; print_p1_p2(Before p1.swap(p2): ); p1.swap(p2); print_p1_p2(After p1.swap(p2): ); swap(p1, p2); print_p1_p2(After swap(p1, p2): ); }输出Before p1.swap(p2): p1 {42, ABCD, 2.71}, p2 {10, 1234, 3.14} After p1.swap(p2): p1 {10, 1234, 3.14}, p2 {42, ABCD, 2.71} After swap(p1, p2): p1 {42, ABCD, 2.71}, p2 {10, 1234, 3.14}10.std::make_from_tuplestd::make_from_tuple是以元组std::tuple的元素作为构造函数的参数构造别的类型对象如下例子#include iostream #include tuple struct Foo { Foo(int first, float second, int third) { std::cout first , second , third \n; } }; int main() { auto tuple std::make_tuple(42, 3.14f, 0); std::make_from_tupleFoo(std::move(tuple)); }输出42, 3.14, 011.项目实战11.1.std::tuple的序列化和反序列化利用QDataStream的序列化数据重写操作符operator和operator代码如下templatetypename... Ts QDataStream operator(QDataStream dataStream, std::tupleTs... const theTuple) { std::apply ( [dataStream](Ts const... tupleArgs){ ((dataStream tupleArgs), ...); }, theTuple ); return dataStream; } templatetypename... Ts QDataStream operator(QDataStream dataStream, std::tupleTs... theTuple) { std::apply ( [dataStream](Ts... tupleArgs){ ((dataStream tupleArgs), ...); }, theTuple ); return dataStream; } 或 templatetypename... Ts QDataStream operator(QDataStream dataStream, std::tupleTs... const theTuple) { std::apply ( [dataStream](Ts const... tupleArgs){ (dataStream ... tupleArgs); }, theTuple ); return dataStream; } templatetypename... Ts QDataStream operator(QDataStream dataStream, std::tupleTs... theTuple) { std::apply ( [dataStream](Ts... tupleArgs){ (dataStream ... tupleArgs); }, theTuple ); return dataStream; }11.2.获取std::tuple的数据大小获取std::tuple的实际内容大小但是std::tuple不能包含可变内容长度字段代码如下templateclass Tuple, std::size_t N struct stTupleContentSize{ using first typename std::tuple_elementN-1, Tuple::type; using others stTupleContentSizeTuple, N-1; static constexpr std::size_t sizeof(first) others::size; }; templateclass Tuple struct stTupleContentSizeTuple, 1{ using first typename std::tuple_element0, Tuple::type; static constexpr std::size_t sizeof(first); };调用方法using queryWaveParamData std::tupleint, bool, double, long; constexpr int queryWaveParamDataSize stTupleContentSizequeryWaveParamData, std::tuple_size_vqueryWaveParamData::size;12.总结std::tuple 是一种重要的数据结构可以用于在函数参数之间传递数据也可以作为函数的返回值。在实际项目中我们可以灵活地使用 std::tuple以简化代码提高程序的性能。后面我们将继续通过分析std::tuple源码的方式来更深层次讲解它的实现原理值得期待哦。。。参考std::tuple - cppreference.com