C++ structured binding
考虑对一个tuple的读取,如下代码:
int a; char b; double c; std::tuple<int,char,double> tp = std::make_tuple(1, 'a', 2.3); a = std::get<0>(tp); b = std::get<1>(tp); c = std::get<2>(tp);
如果你认为tuple用处没有pair大,则有pair的代码:
int x; bool y; std::pair<int, bool> pp = std::make_pair(100,true); x = pp.first; x = std::get<0>(pp); y = pp.second; y = std::get<1>(pp);
C++11给出了简洁的方式,如下代码:
int x; bool y; std::tie(x,y) = std::make_pair(100,true);
C++17给出更简洁的方式,如下代码:
auto[x,y] = std::make_pair(100,true);
这就是结构化的绑定(SB)的第二种形式。SB可以绑定数组的元素(第一种形式),SB绑定结构体的数据成员(第三种形式)。
有啥用呢?就是简化代码!方便程序员写代码。
考虑最早的循环遍历一个map,代码如下:
std::map<int, std::string> mymap{ {1,"hello"},{2," "},{3,"world"} }; std::map<int, std::string>::iterator ite_beg = mymap.begin(); std::map<int, std::string>::iterator ite_end = mymap.end(); for ( ; ite_beg != ite_end; ++ite_beg) { std::pair<int, std::string>& pp = *ite_beg; std::cout << pp.first << " " << pp.second << std::endl; }
看着很高大上的,能蒙一下不懂技术的老板多掏钱给你。其实,这段代码很low,就是写个迭代器类型,拷贝粘贴就完成了。
C++11 引入auto后,可以不用迭代器的类型了,代码如下:
std::map<int, std::string> mymap{ { 1,"hello" },{ 2," " },{ 3,"world" } }; auto ite_beg = mymap.begin(); auto ite_end = mymap.end(); for (; ite_beg != ite_end; ++ite_beg) { auto& pp = *ite_beg; std::cout << pp.first << " " << pp.second << std::endl; }
C++11引入新式的for循环语法后,可以让编译器替你展开这种繁文缛节的定式代码。
std::map<int, std::string> mymap{ { 1,"hello" },{ 2," " },{ 3,"world" } }; for ( auto& pp : mymap) { std::cout << pp.first << " " << pp.second << std::endl; }
一切都很嗨,就是pp.first,pp.second很不爽,这时候,结构化绑定就来帮忙了。代码如下:
std::map<int, std::string> mymap{ { 1,"hello" },{ 2," " },{ 3,"world" } }; for ( auto& pp : mymap) { auto[a,b] = pp; std::cout << a << " " << b << std::endl; }
C++17看来,pp也是很多余的,进一步简化代码如下:
for ( auto[a,b] : mymap) { std::cout << a << " " << b << std::endl; }
结构化绑定支持自定义类,但是需要自定义类暴露公有成员。考虑一个有继承的情况:
#include <vector> #include <string> class Config_Base { std::string name; std::size_t id; std::vector<std::string> data; public: auto get_name(){ return std::string_view(name); } auto get_id(){ return id; } auto get_data(){ return (data); } }; class Config : private Config_Base{ }; int main(){ Config cfg; auto& [a,b,c] = cfg; //编译失败,原因:私有成员看不到呀。 }
我们需要改造一下,骗过编译器。让我们的自定义类的行为好像是tuple一般。怎么骗呢?代码如下:
#include <vector> #include <string> #include <iostream> class Config_Base { std::string name="hello"; std::size_t id=123; std::vector<std::string> data; public: auto get_name() const{ return std::string_view(name); } auto get_id() const{ return id; } auto get_data() const{ return (data); //括起来,就是返回引用,很神奇。 } }; class Config : private Config_Base{ //... public: template <std::size_t N> decltype(auto) get() const { if constexpr (N == 0) return get_name(); else if constexpr (N == 1) return get_id(); else if constexpr (N == 2) return get_data(); } }; namespace std { template<> struct tuple_size<Config> : std::integral_constant<std::size_t, 3> {}; /*等价于 template<> struct tuple_size<Config>{ static const int value = 3; }; */ } namespace std { template<std::size_t N> struct tuple_element<N, Config> { using type = decltype(std::declval<Config>().get<N>()); }; /*等价于 template<> struct tuple_element<0,Config> { using type = std::string_view; }; template<> struct tuple_element<1,Config> { using type = std::size_t; }; template<> struct tuple_element<2,Config> { using type = const std::vector<std::string>&; }; */ } int main(){ Config cfg; auto& [a,b,c] = cfg; std::cout << a << std::endl; }
结构化绑定易错陷阱,考虑如下代码:
1 #include <iostream> 2 3 struct S { 4 int i = 0; 5 ~S(){ std::cout << "~S() i=" << i << std::endl; } 6 }; 7 8 S makeS(){ 9 S s; 10 return s; 11 } 12 13 int main() 14 { 15 auto& [ d ] = makeS(); 16 d++; 17 }
第15行编译失败。用伪代码展开,可以看到:
1 auto & __tmp = makeS(); 2 int& d = __tmp.i;
第1行的auto&是程序员输入的,显然是错误的。因为左值引用不能抓右值。
第2行的int&是编译器设定的(也是语言规范要求的),i是对__tmp.i的引用。
参考:
https://github.com/tvaneerd/cpp17_in_TTs/blob/master/ALL_IN_ONE.md
https://blog.tartanllama.xyz/structured-bindings/