容器的一般性操作

本节解释了关于Hana容器的几个重要概念:如何创建它们,元素的生命周期和其他问题。

创建容器

虽然在C++中创建对象的通常方式是使用它的构造函数,但异构编程使事情更复杂。 实际上,在大多数情况下,人们对(或甚至不知道)要创建的异构容器的实际类型不感兴趣。 另一方面,虽然可以明确地写出该类型,但是这样做是多余的或繁琐的。 因此,Hana使用从std::make_tuple借用的不同方法来创建新容器。 很像一个可以用std::make_tuple创建一个std::tuple,一个hana::tuple可以用hana::make_tuple创建。 然而,更一般地,Hana中的容器可以用make函数创建:

  1. auto xs = hana::make<hana::tuple_tag>(1, 2.2, 'a', "bcde"s);

事实上,make_tuple只是make<tuple_tag>的一个快捷方式,所以当你离开Hana的命名空间时,你不必键入boost::hana::make<boost::hana::tuple_tag>。 简单地说,make<...>是围绕着类型使用的,以创建不同类型的对象,从而概括了一系列std::make_xxx函数。 例如,可以使用make<range_tag>创建一个hana::range的编译时整数:

  1. constexpr auto r = hana::make<hana::range_tag>(hana::int_c<3>, hana::int_c<10>);
  2. static_assert(r == hana::make_range(hana::int_c<3>, hana::int_c<10>), "");

带有尾部_tag的这些类型是表示异类容器族(hana::tuplehana::map等)的虚拟类型。 有关tag部分请参见Hana核心

为方便起见,每当Hana的组件提供make<xxx_tag>函数时,它还提供make_xxx快捷方式以少打一些字符。 此外,在该示例中可以提出的一个有趣的地方是rconstexpr的事实。 通常,每当一个容器只用常量表达式初始化(这是r的情况),该容器可以被标记为constexpr

到目前为止,我们只创建了带有make_xxx系列函数的容器。 然而,一些容器确实提供构造函数作为其接口的一部分。 例如,可以创建一个hana::tuple,就像创建一个std::tuple

  1. hana::tuple<int, double, char, std::string> xs{1, 2.2, 'a', "bcde"s};

当构造函数(或任何真实成员函数)是公共接口的一部分时,它们将在每个容器的基本声明上文档化。 然而,在一般情况下,不应当理所当然的认为可以构建为上面构造的元组的容器。 例如,尝试创建一个hana::range的方式将无法工作:

  1. hana::range<???> xs{hana::int_c<3>, hana::int_c<10>};

事实上,我们甚至不能在这种情况下指定我们想要创建的对象的类型,因为hana::range的精确类型是由实现来定义的,下一节有所提及。

容器类型

本节的目的是澄清从Hana的容器的类型可以预期什么。 事实上,到目前为止,我们总是让编译器通过使用make_xxx系列函数和auto来推导出容器的实际类型。 但一般来说,我们怎样描述一个容器的类型呢?

  1. auto xs = hana::make_tuple(1, '2', "345");
  2. auto ints = hana::make_range(hana::int_c<0>, hana::int_c<100>);
  3. // what can we say about the types of `xs` and `ints`?

答案是看它依赖什么。 一些容器具有明确定义的类型,而其他容器没有指定。 在本示例中,由make_tuple返回的对象的类型是明确定义的,而由make_range返回的类型是由实现定义的:

  1. hana::tuple<int, char, char const*> xs = hana::make_tuple(1, '2', "345");
  2. auto ints = hana::make_range(hana::int_c<0>, hana::int_c<100>);
  3. // can't specify the type of ints, however

它们将在每个容器的基本声明上文档化; 当容器具有实现定义的表示时,在容器的描述中包括可以从该表示中精确地解释可以期望什么的注释。 保留未指定容器的表示有几个原因; 他们在rationales中解释。 当容器的表示是实现定义的时,必须小心不要对它做任何假设,除非这些假设在容器的文档中被明确允许。

容器类型重载

虽然有必要,保留一些未指定的容器类型使得一些事情很难实现,像异构容器上的重载函数:

  1. template <typename T>
  2. void f(std::vector<T> xs) {
  3. // ...
  4. }
  5. template <typename ...???>
  6. void f(unspecified-range-type<???> r) {
  7. // ...
  8. }

is_a实用程序提供了这个原因(和其他方面)。 is_a允许使用其标签检查类型是否是精确类型的容器,而不考虑容器的实际类型。 例如,上面的例子可以重写为

  1. template <typename T>
  2. void f(std::vector<T> xs) {
  3. // ...
  4. }
  5. template <typename R, typename = std::enable_if_t<hana::is_a<hana::range_tag, R>()>>
  6. void f(R r) {
  7. // ...
  8. }

这样,只有当R是一个标签为range_tag的类型时,f的第二个重载才会匹配,而不管该范围的确切表示。 当然,is_a可以用于任何类型的容器:tuplemapset等等。

容器元素

Hana容器拥有自己的元素。 创建容器时,它会创建用于初始化它的元素的副本,并将它们存储在容器中。 当然,可以通过使用移动语义来避免不必要的副本。 由于拥有语义,容器内的对象的生命周期与容器的生命周期相同。

  1. std::string hello = "Hello";
  2. std::vector<char> world = {'W', 'o', 'r', 'l', 'd'};
  3. // hello is copied, world is moved-in
  4. auto xs = hana::make_tuple(hello, std::move(world));
  5. // s is a reference to the copy of hello inside xs.
  6. // It becomes a dangling reference as soon as xs is destroyed.
  7. std::string& s = xs[0_c];

与标准库中的容器非常相似,Hana中的容器期望它们的元素是对象。 因此,引用可能不会存储在其中。 当引用必须存储在容器中时,应该使用std::reference_wrapper

  1. std::vector<int> ints = { /* huge vector of ints */ };
  2. std::vector<std::string> strings = { /* huge vector of strings */ };
  3. auto map = hana::make_map(
  4. hana::make_pair(hana::type_c<int>, std::ref(ints)),
  5. hana::make_pair(hana::type_c<std::string>, std::ref(strings))
  6. );
  7. auto& v = map[hana::type_c<int>].get();
  8. BOOST_HANA_RUNTIME_CHECK(&v == &ints);