c++17之提供类似tuple API给结构化绑定

30 篇文章 17 订阅

如前所述,您可以像标准库为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';
}

结果如下:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值