如前所述,您可以像标准库为std::pair<>、std::tuple<>和std::array<>提供类似于元组的API一样,为任何类型添加对结构化绑定的支持。
1. 只读结构化绑定
下面的示例演示如何为类型Customer进行结构化绑定,其定义如下:
customer1.hpp
#include <iostream>
#include <string>
#include <utility> // for std::move()
class Customer {
private:
std::string first;
std::string last;
long val;
public:
Customer (std::string f, std::string l, long v)
: first{std::move(f)}, last{std::move(l)}, val{v} {
}
std::string getFirst() const {
return first;
}
std::string getLast() const {
return last;
}
long getValue() const {
return val;
}
};
提供一个类似tuple API如下:
structbind1.hpp
#include "customer1.hpp"
#include <utility> // for tuple-like API
// provide a tuple-like API for class Customer for structured bindings:
template<>
struct std::tuple_size<Customer> {
static constexpr int value = 3; // we have 3 attributes
};
template<>
struct std::tuple_element<2, Customer> {
using type = long; // last attribute is a long
};
template<std::size_t Idx>
struct std::tuple_element<Idx, Customer> {
using type = std::string; // the other attributes are strings
};
// define specific getters:
template<std::size_t> auto get(const Customer& c);
template<> auto get<0>(const Customer& c) { return c.getFirst(); }
template<> auto get<1>(const Customer& c) { return c.getLast(); }
template<> auto get<2>(const Customer& c) { return c.getValue(); }
在这里,我们为customer的3个属性定义了一个类似tuple元组的API,它本质上映射到一个Customer的3个getter(任何其他用户定义的映射都是可能的):
std::string first; std::string last; long val; 三个成员变量。
属性的个数定义为customer类型的std::tuple_size的具体化:
template<>
struct std::tuple_size<Customer> {
static const int value = 3; // we have 3 attributes
};
属性的类型定义为std::tuple_element:
template<>
struct std::tuple_element<2, Customer> {
using type = long; // last attribute is a long
};
template<std::size_t Idx>
struct std::tuple_element<Idx, Customer> {
using type = std::string; // the other attributes are strings
};
第三个属性的类型被定义为索引2的具体化。对于其他属性,我们使用部分具体化,它的优先级低于具体化。
最后,我们将相应的getter定义为get<>()函数的重载,该函数位于与Customer类型相同的名称空间中:
template<std::size_t> auto get(const Customer& c);
template<> auto get<0>(const Customer& c) { return c.getFirst(); }
template<> auto get<1>(const Customer& c) { return c.getLast(); }
template<> auto get<2>(const Customer& c) { return c.getValue(); }
在本例中,我们有一个主函数模板声明,并为所有情况提供了完整的专门化。
注意,所有函数模板的集体化都必须使用相同的签名(包括完全相同的返回类型)。原因是我们只提供特定的“实现”,没有新的声明。以下内容无法编译:
template<std::size_t> auto get(const Customer& c);
template<> std::string get<0>(const Customer& c) { return c.getFirst(); }
template<> std::string get<1>(const Customer& c) { return c.getLast(); }
template<> long get<2>(const Customer& c) { return c.getValue(); }
通过使用新的编译时if特性,我们可以将get<>()实现合并为一个函数:
template<std::size_t I> auto get(const Customer& c)
{
static_assert(I < 3);
if constexpr (I == 0) {
return c.getFirst();
}
else if constexpr (I == 1) {
return c.getLast();
}
else { // I == 2
return c.getValue();
}
}
使用这个API,我们可以像往常一样对Customer类型的对象使用结构化绑定:
structbind1.cpp
#include "structbind1.hpp"
#include <iostream>
int main()
{
Customer c{"Tim", "Starr", 42};
auto [f, l, v] = c;
std::cout << "f/l/v: " << f << ' ' << l << ' ' << v << '\n';
// modify structured bindings:
std::string s{std::move(f)};
l = "Waters";
v += 10;
std::cout << "f/l/v: " << f << ' ' << l << ' ' << v << '\n';
std::cout << "c: " << c.getFirst() << ' '
<< c.getLast() << ' ' << c.getValue() << '\n';
std::cout << "s: " << s << '\n';
}
通常,结构化绑定f、l和v是对用c初始化的新匿名实体的“成员”的引用。初始化为每个成员/属性调用相应的getter一次。
因此,在初始化结构化绑定之后,修改c对它们没有影响(反之亦然)。
因此,程序输出如下::
2. 可写结构化绑定
类似tuple元组API可以使用产生引用的函数。这允许使用具有写访问的结构化绑定。考虑class Customer提供了一个API来读取和修改它的成员:
customer2.hpp
#include <string>
#include <utility> // for std::move()
class Customer {
private:
std::string first;
std::string last;
long val;
public:
Customer (std::string f, std::string l, long v)
: first{std::move(f)}, last{std::move(l)}, val{v} {
}
const std::string& firstname() const {
return first;
}
std::string& firstname() {
return first;
}
const std::string& lastname() const {
return last;
}
std::string& lastname() {
return last;
}
long value() const {
return val;
}
long& value() {
return val;
}
};
对于读写访问,我们必须重载常量和非常量引用的getter:
structbind2.hpp
#include "customer2.hpp"
#include <utility> // for tuple-like API
// provide a tuple-like API for class Customer for structured bindings:
template<>
struct std::tuple_size<Customer> {
static constexpr int value = 3; // we have 3 attributes
};
template<>
struct std::tuple_element<2, Customer> {
using type = long; // last attribute is a long
};
template<std::size_t Idx>
struct std::tuple_element<Idx, Customer> {
using type = std::string; // the other attributes are strings
};
// define specific getters:
template<std::size_t I> decltype(auto) get(Customer& c) {
static_assert(I < 3);
if constexpr (I == 0) {
return c.firstname();
}
else if constexpr (I == 1) {
return c.lastname();
}
else { // I == 2
return c.value();
}
}
template<std::size_t I> decltype(auto) get(const Customer& c) {
static_assert(I < 3);
if constexpr (I == 0) {
return c.firstname();
}
else if constexpr (I == 1) {
return c.lastname();
}
else { // I == 2
return c.value();
}
}
template<std::size_t I> decltype(auto) get(Customer&& c) {
static_assert(I < 3);
if constexpr (I == 0) {
return std::move(c.firstname());
}
else if constexpr (I == 1) {
return std::move(c.lastname());
}
else { // I == 2
return c.value();
}
}
注意,您应该拥有所有这三种重载,以便能够处理常量、非常量和可移动对象。要使返回值成为引用,您应该使用decltype(auto).
同样,我们使用新的编译时if特性,如果getter具有不同的返回类型,那么实现将非常简单。如果没有,我们将再次需要完全的j具体化,例如:
template<std::size_t> decltype(auto) get(Customer& c);
template<> decltype(auto) get<0>(Customer& c) { return c.firstname(); }
template<> decltype(auto) get<1>(Customer& c) { return c.lastname(); }
template<> decltype(auto) get<2>(Customer& c) { return c.value(); }
再次注意,主函数模板声明和完整的专门化必须具有相同的签名(包括相同的返回类型)。以下内容无法编译:
template<std::size_t> decltype(auto) get(Customer& c);
template<> std::string& get<0>(Customer& c) { return c.firstname(); }
template<> std::string& get<1>(Customer& c) { return c.lastname(); }
template<> long& get<2>(Customer& c) { return c.value(); }
现在,可以使用结构化绑定进行读取访问和修改成员:
structbind2.cpp
#include "structbind2.hpp"
#include <iostream>
int main()
{
Customer c{"Tim", "Starr", 42};
auto [f, l, v] = c;
std::cout << "f/l/v: " << f << ' ' << l << ' ' << v << '\n';
// modify structured bindings via references:
auto&& [f2, l2, v2] = c;
std::string s{std::move(f2)};
f2 = "Ringo";
v2 += 10;
std::cout << "f2/l2/v2: " << f2 << ' ' << l2 << ' ' << v2 << '\n';
std::cout << "c: " << c.firstname() << ' '
<< c.lastname() << ' ' << c.value() << '\n';
std::cout << "s: " << s << '\n';
}
结果如下: