cpp
#include<tuple>
template< class... Types >
class tuple;
类模板 std::tuple 是固定大小的异类值汇集。它是 std::pair 的推广。
Types...是tuple所存储的元素的类型。支持空列表。
构造函数
cpp
//对 tuple<Types...> 的 每个元素进行默认构造
//要求:Types... 都可默认构造
constexpr tuple();
//直接构造
tuple( const Types&... args );
template< class... UTypes >
tuple( UTypes&&... args );
//从另一个 tuple 拷贝
template< class... UTypes >
tuple( const tuple<UTypes...>& other );
template <class... UTypes>
tuple( tuple<UTypes...>&& other );
//pair → tuple
template< class U1, class U2 >
tuple( const pair<U1,U2>& p );
template< class U1, class U2 >
tuple( pair<U1,U2>&& p );
tuple( const tuple& other ) = default;
tuple( tuple&& other ) = default;
//把 allocator 传递给每个支持 allocator 的元素
template< class Alloc >
tuple( std::allocator_arg_t, const Alloc& a );
template< class Alloc >
tuple( std::allocator_arg_t, const Alloc& a,
const Types&... args );
template< class Alloc, class... UTypes >
tuple( std::allocator_arg_t, const Alloc& a,
UTypes&&... args );
template <class Alloc, class... UTypes>
tuple( std::allocator_arg_t, const Alloc& a,
const tuple<UTypes...>& other );
template< class Alloc, class... UTypes >
tuple( std::allocator_arg_t, const Alloc& a,
tuple<UTypes...>&& other );
template< class Alloc, class U1, class U2 >
tuple( std::allocator_arg_t, const Alloc& a,
const pair<U1, U2>& p ); (15)
template< class Alloc, class U1, class U2 >
tuple( std::allocator_arg_t, const Alloc& a,
pair<U1, U2>&& p );
template< class Alloc >
tuple( std::allocator_arg_t, const Alloc& a,
const tuple& other );
template< class Alloc >
tuple( std::allocator_arg_t, const Alloc& a,
tuple&& other );
std::tuple 可以显式指定引用类型
cpp
int x = 10;
std::tuple<int&> t(x); // OK
std::get<0>(t) = 20;
std::cout << x; // 20
非成员函数
std::make_tuple
cpp
template< class... Types >
std::tuple<VTypes...> make_tuple( Types&&... args );
创建 tuple 对象,从参数类型推导目标类型。
对于每个 Types... 中的 Ti , Vtypes... 中的对应类型 Vi 为 std::decay<Ti>::type ,除非应用 std::decay 对某些类型 X 导致 std::reference_wrapper<X> ,该情况下推导的类型为 X& 。
std::make_tuple 默认会"值拷贝一切"
只有当你显式用 std::ref / std::cref 包装参数 时,
它才会在 tuple 里保存 引用 (T&)。
返回值
含给定值的 std::tuple 对象,如同用 std::tuple<VTypes...>(std::forward<Types>(t)...). 创建
示例
cpp
#include <iostream>
#include <tuple>
#include <functional>
std::tuple<int, int> f() // 此函数返回多值
{
int x = 5;
return std::make_tuple(x, 7); // C++17可以写成return {x,7};
}
int main()
{
// 异类 tuple 构造
int n = 1;
auto t = std::make_tuple(10, "Test", 3.14, std::ref(n), n);
n = 7;
std::cout << "The value of t is " << "("
<< std::get<0>(t) << ", " << std::get<1>(t) << ", "
<< std::get<2>(t) << ", " << std::get<3>(t) << ", "
<< std::get<4>(t) << ")\n";
// 返回多值的函数
int a, b;
std::tie(a, b) = f();
std::cout << a << " " << b << "\n";
}
输出:
bash
The value of t is (10, Test, 3.14, 7, 1)
5 7
注意用std::ref包裹的n,n修改后,取出的值是7,说明std::ref起了效果。
为什么默认make_tuple禁止引用推导?
标准库的态度是:
❝ 你必须明确说"我要引用"
❝ 否则我就当你要的是值
cpp
std::tuple<int&> bad() {
int x = 10;
return std::make_tuple(x); // 如果推成 int&,直接悬垂引用
}
std::tie
cpp
template< class... Types >
std::tuple<Types&...> tie( Types&... args ) noexcept;
template< class... Types >
constexpr std::tuple<Types&...> tie( Types&... args ) noexcept;
创建到参数或 std::ignore 实例的左值引用的 tuple 。
std::tie 用来把一组变量"绑成一个 tuple 的引用视图"
不拷贝、不拥有,只是"引用包装"。
std::ignore
std::ignore 是 <tuple> 里提供的一个"占位用的左值",主要用途只有一个:
在解包(赋值 / 绑定)时,明确表示"这个值我不要"
没有 std::ignore 的问题
cpp
int a, b;
std::tie(a, b) = std::make_tuple(1, 2);
如果你只想要 b,那你还是得写一个变量接 a。
用 std::ignore
cpp
int b;
std::tie(std::ignore, b) = std::make_tuple(1, 2);
含义非常明确:
第一个值我不要,丢掉
经典场景:insert / emplace
cpp
auto [it, inserted] = m.insert({1, "one"});
如果你只关心是否插入成功:
cpp
bool inserted;
std::tie(std::ignore, inserted) = m.insert({1, "one"});
返回值
含左值引用的 std::tuple 对象。
示例
cpp
struct S {
int n;
std::string s;
float d;
//这个比较类似于字典序,先比第一个成员n,再比第二个成员s,以此类推
//因为std::tuple内置的比较运算符就是这个逻辑
//并且使用std::tie而不是std::make_tuple省略了拷贝开销
bool operator<(const S& rhs)const {
return std::tie(n, s, d) < std::tie(rhs.n, rhs.s, rhs.d);
}
};
std::set<S> set_of_s;//S已经实现了小于运算符
S value{ 42,"Test",3.14 };
std::set<S>::iterator iter;
bool inserted;
//iter和inserted这两个变量的值会被改变
std::tie(iter, inserted) = set_of_s.insert(value);
if (inserted)
std::cout << "Value was inserted successfully\n";
其实这个std::tie承接insert的值,用.first/second也完全可以实现
并且到了C++17后,出现了结构化绑定,如果仅仅需要语法糖的话,可以这么写
cpp
auto [iter, inserted] = set_of_s.insert(value);
但是auto的iter和inserted并不会和外部变量绑定,这是两个新的变量。所以如果给外部变量赋值,也可以继续用std::tie。
std::forward_as_tuple
cpp
template< class... Types >
tuple<Types&&...> forward_as_tuple( Types&&... args ) noexcept;
template< class... Types >
constexpr tuple<Types&&...> forward_as_tuple( Types&&... args ) noexcept;
它把一组参数包装成一个 tuple,但不拷贝,保留了每个参数的引用类型(左值/右值),方便"完美转发"给函数。
返回值
如同以 std::tuple<Types&&...>(std::forward<Types>(args)...) 创建的 std::tuple 对象
注意事项:临时对象不要存太久, 所以 forward_as_tuple 只适合立即转发,不适合长期保存。
示例
cpp
#include <iostream>
#include <map>
#include <tuple>
#include <string>
int main()
{
std::map<int, std::string> m;
m.emplace(std::piecewise_construct,
std::forward_as_tuple(10),
std::forward_as_tuple(20, 'a'));
std::cout << "m[10] = " << m[10] << '\n';
// 下面是错误:它产生保有二个悬垂引用的 std::tuple<int&&, char&&>
//
// auto t = std::forward_as_tuple(20, 'a');
// m.emplace(std::piecewise_construct, std::forward_as_tuple(10), t);
}
这个例子里,std::piecewise_construct 是一个 构造标签(tag) ,告诉 std::pair:
"请分别用两个 tuple,逐个成员地构造 first 和 second"
所以std::forward_as_tuple(20, 'a')实际是构造了一个string(20,'a')
那为什么下面的是错误的?因为auto t的类型是std::tuple<int&&, char&&>,但是它引用的20和'a'在这一行结束后,就销毁了。
所以在下一行构造的时候,t 内部保存的是:
(int&&) -> 指向已经销毁的 20
(char&&) -> 指向已经销毁的 'a'
也就是:
❌ 悬垂引用(dangling reference)
std::tuple_cat
cpp
template< class... Tuples >
std::tuple<CTypes...> tuple_cat(Tuples&&... args);
template< class... Tuples >
constexpr std::tuple<CTypes...> tuple_cat(Tuples&&... args);
构造所有 args 中的 tuple 所连接成 tuple 。
若 std::decay_t<Tuples>... 中的任何类型不是 std::tuple 的特化则行为未定义。然而,实现可以选择支持遵循仿 tuple 协议的类型(例如 std::array 与 std::pair )。
说人话就是把几个tuple合并成一个tuple
返回值
std::tuple 对象,由所有参数 tuple 的所有元素组成,对于每个独立元素构造自 std::get<i>(std::forward<Ti>(arg)) 。
示例
cpp
#include <iostream>
#include <tuple>
#include <utility>
// 打印任何大小 tuple 的辅助类
template<class Tuple,std::size_t N>
struct TuplePrinter {
static void print(const Tuple& t) {
TuplePrinter<Tuple, N - 1>::print(t);
std::cout << ", " << std::get<N - 1>(t);
}
};
template<class Tuple>
struct TuplePrinter < Tuple, 1> {
static void print(const Tuple& t) {
std::cout << std::get<0>(t);
}
};
//打印入口
template<class...Args>
void print(const std::tuple<Args...>& t) {
std::cout << "(";
TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
std::cout << ")\n";
}
int main() {
std::tuple<int, 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;//注意std::tie构造的tuple引用了n,所以这里修改n会影响t2
print(t2);
}
结果
bash
(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)
当然以上的辅助类是C++11的写法,到了C++17,有了折叠表达式,可以写成这样
cpp
#include <iostream>
#include <tuple>
#include <utility>
template <typename Tuple, std::size_t... Is>
void print_impl(const Tuple& t, std::index_sequence<Is...>) {
std::cout << "(";
((std::cout << (Is == 0 ? "" : ", ") << std::get<Is>(t)), ...);
std::cout << ")\n";
}
template <typename... Args>
void print(const std::tuple<Args...>& t) {
print_impl(t, std::index_sequence_for<Args...>{});
}
int main() {
std::tuple<int, 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;//注意std::tie构造的tuple引用了n,所以这里修改n会影响t2
print(t2);
}
当然由于我的模板也是半吊子,我也每太看懂。
std::get
cpp
template< std::size_t I, class... Types >
typename std::tuple_element<I, tuple<Types...> >::type&
get( tuple<Types...>& t ) noexcept; //(1)
template< std::size_t I, class... Types >
typename std::tuple_element<I, tuple<Types...> >::type&&
get( tuple<Types...>&& t ) noexcept; //(2)
template< std::size_t I, class... Types >
typename std::tuple_element<I, tuple<Types...> >::type const&
get( const tuple<Types...>& t ) noexcept; //(3)
template< std::size_t I, class... Types >
typename std::tuple_element<I, tuple<Types...> >::type const&&
get( const tuple<Types...>&& t ) noexcept; //(4)
template< class T, class... Types >
constexpr T& get(tuple<Types...>& t) noexcept;//(5)
template< class T, class... Types >
constexpr T&& get(tuple<Types...>&& t) noexcept;//(6)
template< class T, class... Types >
constexpr const T& get(const tuple<Types...>& t) noexcept;//(7)
template< class T, class... Types >
constexpr const T&& get(const tuple<Types...>&& t) noexcept;//(8)
1-4) 从 tuple 提取第 I 个元素。 I 是 [0, sizeof...(Types)) 中的整数值。
5-8) 提取 tuple t 的类型为 T 的元素。若 tuple 不恰好拥有一个该类型元素则编译失败。
返回值
到 t 的被选中元素的引用。
示例
cpp
#include <iostream>
#include <string>
#include <tuple>
int main()
{
auto t = std::make_tuple(1, "Foo", 3.14);
// 基于下标的访问
std::cout << "(" << std::get<0>(t) << ", " << std::get<1>(t)
<< ", " << std::get<2>(t) << ")\n";
// 基于类型的访问( C++14 起)
std::cout << "(" << std::get<int>(t) << ", " << std::get<const char*>(t)
<< ", " << std::get<double>(t) << ")\n";
// 注意: std::tie 和结构化绑定亦可用于分解 tuple
}
输出:
cpp
(1, Foo, 3.14)
(1, Foo, 3.14)
辅助类
std::tuple_size<std::tuple>
cpp
template< class... Types >
struct tuple_size< std::tuple<Types...> >
: std::integral_constant<std::size_t, sizeof...(Types)> { };
template <class _Ty>
constexpr size_t tuple_size_v = tuple_size<_Ty>::value;
提供对 tuple 中元素数量的访问,作为编译时常量表达式。
继承自 std::integral_constant
成员常量
|--------------|-------------------------------|
| value [静态] |sizeof...(Types)(公开静态成员常量) |成员函数
|----------------------|----------------------------------------|
| operator std::size_t | 转换对象为 std::size_t ,返回value(公开成员函数) |
| operator() | 返回value(公开成员函数) |成员类型
|--------------|----------------------------------------------|
| 类型 | 定义 |
|value_type|std::size_t|
|type| std::integral_constant<std::size_t, value> |
示例
cpp
#include <iostream>
#include <tuple>
#include <utility>
template<class T>
void test(T value) {
int a[std::tuple_size_v<T>];//编译时可以通过
std::cout << std::tuple_size<T>{}<<" ";
std::cout << std::tuple_size<T>{}()<<" ";//加个括号执行也可以
std::cout << sizeof(a) << " ";
}
int main() {
test(std::make_tuple(1, 2, 3.14));
}
输出
bash
3 3 12
std::tuple_element<std::tuple>
cpp
template< std::size_t I, class... Types >
class tuple_element< I, tuple<Types...> >;
提供 tuple 元素类型的编译时带下标访问。
成员类型
|------|-------------------------------------------------------|
| 成员类型 | 定义 |
| type | tuple 的第 I 元素的类型,其中 I 在 [0, sizeof...(Types)) 中 |
示例
cpp
#include <iostream>
#include <tuple>
#include <utility>
template<class...Args>
struct type_list {
template<std::size_t N>
using type1 = typename std::tuple_element<N, std::tuple<Args...>>::type;
//type1和type2是一样的
template<std::size_t N>
using type2 = typename std::tuple_element_t<N, std::tuple<Args...>>;
};
int main() {
std::cout << std::boolalpha;
type_list<int, char, bool>::type1<2> x = true;
type_list<int, char, bool>::type2<2> y = false;
std::cout << x << " " <<y << '\n';
}
输出
cpp
true false