(点击上方公众号,可快速关注)
C++没有模式匹配,在提取某个数据类型的元素或成员时会显得不太方便。假设t
是一个包含3个元素的元组,类型为std::tuple
,取每个元素的代码如下:
auto i = std::get<0>(t);
auto d = std::get<1>(t);
auto b = std::get<2>(t);
需要三行语句,每行语句的功能基本相同,除了索引不一样,显得冗余;相信很多同学的做法是:将第一行代码复制两份,在此基础上修改为后两句,这样出错就容易多了,万一忘了改索引呢。
上面的例子是关于元组的,std::pair<>
是元组的特列,也能通过通用的std::get
方式获取元素。除此之外,还提供了first
、second
成员变量,较之元组简单了不少,但名字太“抽象”,完全体现不出数据的含义,可读性不高。
我们看下支持模式匹配的Haskell是怎么做的:
let (i, d, b) = t
只用一行代码,是不是特简洁;而且可以给元素命名,可读性也很高。很幸运,现代C++提供了两种工具,能实现与之类似的效果(当然还远不如Haskell强大),下面将分别介绍。
C++11通过标准库的方式提供,用于std::tuple
、std::pair
类型的数据解包。函数签名为:
#include
template< class... Types >
tuple tie( Types&... args ) noexcept;
从参数和返回值类型上能大体猜到这个函数的工作原理:它实际上将绑定变量通过引用方式传入tie
函数,tie函数将这些参数组装成一个tuple,等号右侧的tuple的整体赋值给这个tuple,从而实现tuple每个位置的值都绑定到相应的变量上。
使用方式也比较简单,先声明绑定变量,然后将这些变量传入tie
函数:
int i;
double d;
bool b;
std::tie(i, d, b) = t;
代码已经有点Haskell的味道了,虽然解包的代码少了,但绑定的变量需要事先声明,显得代码不少。
在Haskell中,如果不想捕获某个值,可以使用wildcard。如,程序可能用不到第2个值,可以这么写:
let (i, _, b) = t
std::tie
也提供了类似的功能,使用std::ignore
代替变量即可:
int i;
bool b;
std::tie(i, std::ignore, b) = t;
特别说明下,tie
函数也可以对std::pair
解包,因为std::tuple
重载了赋值操作符,所以赋值操作符右侧为pair的情况也能处理。
template< class U1, class U2 >
constexpr tuple& operator=( const pair& p );
template< class U1, class U2 >
constexpr tuple& operator=( pair&& p );
tie
函数使用起来比较简单,但也没多大亮点。仅仅采用标准库实现的,限制还是比较多的:
绑定变量必须先定义
某些类型的变量可能不太容易声明,这样就很难使用tie
获取元素。
适用范围窄
仅用于tuple和pair
只有拷贝语义
解包时,右侧的元素值只能复制给左侧的值,如果对象较大,复制成本会比较高。
C++17中引入的一个语言特性,叫做Structured Binding,有了它,就不必用std::tie
了。这一部分主要介绍这个新的语言特性的用法,通过示例代码给出。
这个语法支持三种类型的结构的绑定,下面一一说明:
C++主要有两种数组类型:
继承自C语言的原始数组
C++11标准库引入的std::array
类型
都支持结构绑定:
int rawArray[] = {1, 2};
std::array array {3, 4};
auto [x, y] = rawArray; // x=1, y=1
auto [a, b] = array; // a=3, b=4
结构绑定配合auto
关键字使用,方括号内的是元素被绑定的变量名,与数组的元素个数一致。上例中的绑定相当于将数组元素直接拷贝到对应的变量里,语义跟普通的赋值操作是一样的。普通的变量能支持const
、volatile
等修饰符,而且支持引用(&
、&&
),结构绑定同样支持。
std::array array {std::string("a"), std::string("b")};
const auto& [a, b] = array; // a="a", b="b"
上例中,a
和b
是常量字符串的引用类型。注意const
、&
和auto
三者之间的位置,可以根据实际需要省略const
、&
,语义跟普通的变量是一致的,在后续的示例中就不突出这些用法了。
tuple-like类型是指行为跟tuple一致的类型,怎么才算行为一致,这里留个坑,后续填。现在只需知道标准库的std::tuple
和std::pair
肯定是tuple-like类型即可。
用法示例如下:
auto p = std::make_pair(1, 2);
auto t = std::make_tuple(3, 4);
auto [x, y] = p; // x=1, y=1
auto [a, b] = t; // a=3, b=4
还能绑定结构体数据成员,需要保证被绑定的数据成员是public
的。
struct Person {
std::string name;
int age;
};
Person p {"name", 66};
auto [n, a] = p; // n="name", a=66
是不是很简单。
std::tie
和Structured Binding之间的选择问题:如果编译器支持C++17,当然推荐后者。在std::tie
一节末尾列出的3个限制,Structured Binding都比较完美地解决了,而且代码简洁清晰。保持代码简洁、可读性好就是提高生产力。
喜欢我的文章,请关注我的公众号。转载请标明出处。