(点击上方公众号,可快速关注)
劳动节快乐!打工人加油!
上一篇文章《使代码简洁的两种解包方式》介绍了两种代码简洁工具,尤其推荐Structured Bindings,因为它能绑定各种类型的成员。那篇文章介绍的用法基本能覆盖我们的日常应用了,本篇主要查缺补漏,着重介绍自定义类型怎么支持Structured Bindings特性。
主要参考资料是C++20语言规范草案(N4868),草案内容与正式版基本一致,而且完全免费,所以这是大部分C++程序员的参考资料。我已经将N4868上传到百度网盘,可以在公众号里发送“n4868”获取下载地址。
弃用volatile修饰
volatible
修饰绑定变量的用法已经废弃。实际上不止structured bindings,其他地方也越来越不待见这个关键字,比如前缀后缀的++
和--
操作符也废弃了对volatile
变量的操作。
go">struct linhenykus { short forelimb; };
void park(linhenykus alvarezsauroid) {
volatile auto [what_is_this] = alvarezsauroid; // deprecated
// ...
}
类数据成员的一些限制
除了保证数据成员是public
外,还有一些其他的限制规则。
不能绑定静态数据成员
结构绑定只能绑定非静态成员,下面给一个完整的示例:
go">#include <string>
class Object
{
static int newCount;
std::string name;
std::string hashCode;
};
int Object::newCount = 0;
int main()
{
Object obj{"person", "xyz"};
auto &[name, hashCode] = obj; // 只能绑定非静态成员,newCount没法绑定
return 0;
}
也能看出来,非静态成员的数量跟绑定变量的数量是一致的,否则会出错;而且,绑定顺序跟数据成员的声明顺序是一致的。
类不能包含匿名的联合(union)成员
看下面的代码示例,没法对Edge
类对象结构绑定。编译器会在编译阶段推导出绑定变量的类型,这样的好处是不会影响程序的运行效率。若类中包含匿名联合成员,编译阶段没法确定绑定变量的类型。在技术上,要确定每个绑定的类型是可能的,这需要编译器记录更多的信息,在运行时进行判断,势必会影响程序的运行效率,违背了C++零抽象的原则。
go">struct Edge
{
std::string name;
union //包含匿名联合成员的类示例不能结构绑定
{
int n_weight;
double d_weight;
};
};
数据成员归属限制
“数据成员归属”指这个数据是哪个类引入的,可能并不准确,实在想不出其他词 ,有更恰当的词欢迎在评论区指正。
看下面这个例子,会出错:
go">struct Person
{
std::string name;
};
struct Chinese : Person
{
bool bCPC;
};
Chinese ch;
ch.name="me";
ch.bCPC=false;
auto [name,bCPC] = ch; //出错
ch
对象是Chinese
类型,它的name
成员归属为基类,bCPC
归属为自己。这种继承关系比较常见,但结构绑定不能支持。
语言规范对数据成员归属描述如下:
all of
E
’s non-static data members shall be direct members ofE
or of the same base class ofE
, well-formed when named ase .name
in the context of the structured binding
非静态成员,要么是“类型的直接成员”,要么是“基类的直接成员”,不允许例子中这样的混合情况,既有自己的数据成员,还有基类的数据成员。根据标准的限制规则,我们有两种改正方式:
数据成员归属为基类:
go">struct Person
{
std::string name;
bool bCPC;
};
struct Chinese : Person {};
Chinese ch;
ch.name="me";
ch.bCPC=false;
auto [name,bCPC] = ch;
数据成员都为Chinese的直接成员:
go">struct Person {};
struct Chinese : Person
{
std::string name;
bool bCPC;
};
Chinese ch;
ch.name="me";
ch.bCPC=false;
auto [name,bCPC] = ch;
自定义类型
内置的类型成员的限制都可以通过自定义方式破解。自定义实际就是模拟元组的行为,需要实现自己的get<>()
、tuple_size
、tuple_element
。示例如下:
go">#include <iostream>
#include <string>
#include <tuple>
class Person
{
public:
Person(const std::string& name)
: sName(name) {}
std::string& getName()
{ return sName; }
private:
std::string sName;
};
struct Chinese : Person
{
public:
Chinese(const std::string& name, bool cpc)
: Person(name), bCPC(cpc){}
bool getCPC() { return bCPC; }
public:
template <std::size_t N>
decltype(auto) get()
{
if constexpr (N == 0)
return getName();
else if constexpr (N == 1)
return getCPC();
}
private:
bool bCPC;
};
namespace std {
template<>
struct tuple_size<Chinese>
: std::integral_constant<std::size_t, 2> {};
template<std::size_t N>
struct tuple_element<N, Chinese> {
using type =
decltype(std::declval<Chinese>().get<N>());
};
}
int main()
{
Chinese ch("me", false);
auto [name,bCPC] = ch;
return 0;
}
tuple_size
、tuple_element
需要实现在std
命名空间内,get
函数可以定义在类型内,也可以在类型外,上例是在类型内定义的。我们将上例改写为get
在类型外定义:
go">#include <iostream>
#include <string>
#include <tuple>
class Person
{
public:
Person(const std::string& name) : sName(name) {}
std::string& getName() { return sName; }
private:
std::string sName;
};
struct Chinese : Person
{
public:
Chinese(const std::string& name, bool cpc)
: Person(name), bCPC(cpc){}
bool getCPC() { return bCPC; }
private:
bool bCPC;
};
template <std::size_t N>
decltype(auto) get(Chinese& ch)
{
if constexpr (N == 0)
return ch.getName();
else if constexpr (N == 1)
return ch.getCPC();
}
namespace std {
template<>
struct tuple_size<Chinese>
: std::integral_constant<std::size_t, 2> {};
template<>
struct tuple_element<0,Chinese>
{ using type = std::string; };
template<>
struct tuple_element<1,Chinese>
{ using type = bool; };
}
int main()
{
Chinese ch("me", false);
auto& [name,bCPC] = ch;
return 0;
}
有两点需要特别注意:
tuple_element
用了最原始的手段实现,这是因为get
方法已经不是类型的成员了,所以这里枚举了各个成员的类型。get
函数的参数我故意使用了引用类型Chinese& ch
,相应的绑定声明也需要改为引用方式(auto& [name,bCPC]
)。这里要说明的是:绑定声明取决于get
方法的声明,不一致会编译错误。
这块内容在语言规范9.6节第4小点有描述。
欢迎关注我的公众号。转载请标明出处。