C++笔记:std::tuple

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... 中的 TiVtypes... 中的对应类型 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
相关推荐
君生我老1 小时前
C++ list类容器常用操作
开发语言·c++
fpcc1 小时前
跟我学C++中级篇——文件和目录
linux·c++
小龙报2 小时前
【初阶数据结构】解锁顺序表潜能:一站式实现高效通讯录系统
c语言·数据结构·c++·程序人生·算法·链表·visual studio
历程里程碑2 小时前
Linux 1 指令(1)入门:6大基础指令详解
linux·运维·服务器·c语言·开发语言·数据结构·c++
ShineWinsu10 小时前
对于C++:类和对象的解析—下(第二部分)
c++·面试·笔试·对象··工作·stati
BHXDML11 小时前
第七章:类与对象(c++)
开发语言·c++
生擒小朵拉12 小时前
ROS1学习笔记(二)
笔记·学习
yyf1989052512 小时前
C++ 跨平台开发的挑战与应对策略
c++
Root_Hacker12 小时前
include文件包含个人笔记及c底层调试
android·linux·服务器·c语言·笔记·安全·php