目录
tuple(或 n-tuple)是固定大小的元素集合。对、三元组、四元组等都是 tuples。在编程语言中,tuple 是一个数据对象,包含其他对象作为元素。这些元素对象可以是不同的类型。
在许多情况下,Tuples 都很方便。例如,tuples 使定义返回多个值的函数变得容易。
一些编程语言,如 ML、Python 和 Haskell,具有内置的 tuple 构造。不幸的是,C++ 没有。为了弥补这种“缺陷”,Boost Tuple 库使用模板实现了一个 tuple 构造。
要使用该库,只需包含
#include "boost/tuple/tuple.hpp"
比较运算符可以包含在
#include "boost/tuple/tuple_comparison.hpp"
要使用 tuple 输入和输出运算符,
#include "boost/tuple/tuple_io.hpp"
tuple_io.hpp
和 tuple_comparison.hpp
都包含 tuple.hpp
。
所有定义都在命名空间 ::boost::tuples
中,但最常用的名称使用 using 声明提升到命名空间 ::boost
。这些名称是:tuple
、make_tuple
、tie
和 get
。此外,ref
和 cref
直接在 ::boost
命名空间下定义。
tuple 类型是 tuple
模板的实例化。模板参数指定 tuple 元素的类型。当前版本支持 0-10 个元素的 tuples。如果需要,上限可以增加到,例如,几十个元素。数据元素可以是任何 C++ 类型。请注意,void
和普通函数类型是有效的 C++ 类型,但此类类型的对象不存在。因此,如果 tuple 类型包含此类类型作为元素,则 tuple 类型可以存在,但该类型的对象不能存在。对于无法复制或非默认可构造的元素类型存在自然限制(请参阅下面的“构造 tuples”)。
例如,以下定义是有效的 tuple 实例化(A
、B
和 C
是一些用户定义的类)
tuple<int> tuple<double&, const double&, const double, double*, const double*> tuple<A, int(*)(char, int), B(A::*)(C&), C> tuple<std::string, std::pair<A, B> > tuple<A*, tuple<const A*, const B&, C>, bool, void*>
tuple 构造函数将 tuple 元素作为参数。对于 n 元素 tuple,可以使用 k 个参数调用构造函数,其中 0
<= k <= n。例如
tuple<int, double>() tuple<int, double>(1) tuple<int, double>(1, 3.14)
如果未提供元素的初始值,则会默认初始化它(因此必须是默认可初始化的)。例如
class X { X(); public: X(std::string); }; tuple<X,X,X>() // error: no default constructor for X tuple<X,X,X>(string("Jaba"), string("Daba"), string("Duu")) // ok
特别是,引用类型没有默认初始化
tuple<double&>() // error: reference must be // initialized explicitly double d = 5; tuple<double&>(d) // ok tuple<double&>(d+3.14) // error: cannot initialize // non-const reference with a temporary tuple<const double&>(d+3.14) // ok, but dangerous: // the element becomes a dangling reference
对无法复制的元素使用初始值是编译时错误
class Y { Y(const Y&); public: Y(); }; char a[10]; tuple<char[10], Y>(a, Y()); // error, neither arrays nor Y can be copied tuple<char[10], Y>(); // ok
特别注意,以下内容完全可以
Y y; tuple<char(&)[10], Y&>(a, y);
有可能提出无法构造的 tuple 类型。如果无法初始化的元素的索引低于需要初始化的元素,则会发生这种情况。例如:tuple<char[10], int&>
。
总而言之,tuple 构造在语义上只是一组单独的基本构造。
也可以使用 make_tuple
(参见 std::make_pair
)辅助函数构造 Tuples。这使得构造更方便,从而使程序员无需显式指定元素类型
tuple<int, int, double> add_multiply_divide(int a, int b) { return make_tuple(a+b, a*b, double(a)/double(b)); }
默认情况下,元素类型被推导为普通非引用类型。例如
void foo(const A& a, B& b) { ... make_tuple(a, b);
make_tuple
调用产生类型为 tuple<A, B>
的 tuple。
有时不希望使用普通非引用类型,例如,如果元素类型无法复制。因此,程序员可以控制类型推导,并声明应使用对 const 的引用或对非 const 类型的引用作为元素类型。这是通过两个辅助模板函数完成的:boost::ref
和 boost::cref
。可以使用这些函数包装任何参数以获得所需的类型。该机制不会损害 const 正确性,因为使用 ref 包装的 const 对象会导致具有 const 引用类型的 tuple 元素(请参阅下面的第五个示例)。例如
A a; B b; const A ca = a; make_tuple(cref(a), b); // creates tuple<const A&, B> make_tuple(ref(a), b); // creates tuple<A&, B> make_tuple(ref(a), cref(b)); // creates tuple<A&, const B&> make_tuple(cref(ca)); // creates tuple<const A&> make_tuple(ref(ca)); // creates tuple<const A&>
默认情况下,make_tuple
函数的数组参数被推导为对 const 类型的引用;无需使用 cref
包装它们。例如
make_tuple("Donald", "Daisy");
这将创建一个类型为 tuple<const char (&)[7], const char (&)[6]>
的对象(请注意,字符串文字的类型是 const 字符数组,而不是 const char*
)。但是,要让 make_tuple
创建具有非 const 数组类型元素的 tuple,必须使用 ref
包装器。
函数指针被推导为普通非引用类型,即普通函数指针。tuple 也可以保存对函数的引用,但是不能使用 make_tuple
构造这样的 tuple(将产生 const 限定的函数类型,这是非法的)
void f(int i); ... make_tuple(&f); // tuple<void (*)(int)> ... tuple<tuple<void (&)(int)> > a(f) // ok make_tuple(f); // not ok
tuple 元素通过表达式访问
t.get<N>()
或
get<N>(t)
其中 t
是 tuple 对象,N
是一个常量整型表达式,指定要访问的元素的索引。根据 t
是否为 const,get
将第 N
个元素作为对 const 或非 const 类型的引用返回。第一个元素的索引为 0
,因此 N
必须介于 0
和 k-1
之间,其中 k 是 tuple 中元素的数量。违反这些约束将在编译时检测到。例子
double d = 2.7; A a; tuple<int, double&, const A&> t(1, d, a); const tuple<int, double&, const A&> ct = t; ... int i = get<0>(t); i = t.get<0>(); // ok int j = get<0>(ct); // ok get<0>(t) = 5; // ok get<0>(ct) = 5; // error, can't assign to const ... double e = get<1>(t); // ok get<1>(t) = 3.14; // ok get<2>(t) = A(); // error, can't assign to const A aa = get<3>(t); // error: index out of bounds ... ++get<0>(t); // ok, can be used as any variable
[注意: MS Visual C++ 编译器不支持成员 get
函数。此外,编译器在没有显式命名空间限定符的情况下很难找到非成员 get
函数。因此,在编写应使用 MSVC++ 6.0 编译的代码时,所有 get
调用都应限定为 tuples::get<N>(a_tuple)
。]
可以从另一个 tuple 复制构造 tuple,前提是元素类型是逐元素复制构造的。类似地,可以将 tuple 赋值给另一个 tuple,前提是元素类型是逐元素可赋值的。例如
class A {}; class B : public A {}; struct C { C(); C(const B&); }; struct D { operator C() const; }; tuple<char, B*, B, D> t; ... tuple<int, A*, C, C> a(t); // ok a = t; // ok
在这两种情况下,执行的转换是
char
-> int
,
B*
-> A*
(派生类指针到基类指针),B
-> C
(用户定义的转换),以及D
-> C
(用户定义的转换)。请注意,也定义了从 std::pair
类型赋值
tuple<float, int> a = std::make_pair(1, 'a');
Tuples 将运算符 ==
、!=
、<
、>
、<=
和 >=
简化为相应的基本运算符。这意味着,如果在两个 tuples 的所有元素之间定义了这些运算符中的任何一个,则也在 tuples 之间定义了相同的运算符。两个 tuples a
和 b
的相等运算符定义为
a == b
当且仅当对于每个 i
:a
i == b
ia != b
当且仅当存在 i
:a
i != b
i运算符 <
、>
、<=
和 >=
实现字典顺序。
请注意,尝试比较两个长度不同的 tuples 会导致编译时错误。此外,比较运算符是“短路”的:基本比较从第一个元素开始,并且仅在结果明确之前执行。
例子
tuple<std::string, int, A> t1(std::string("same?"), 2, A()); tuple<std::string, long, A> t2(std::string("same?"), 2, A()); tuple<std::string, long, A> t3(std::string("different"), 3, A()); bool operator==(A, A) { std::cout << "All the same to me..."; return true; } t1 == t2; // true t1 == t3; // false, does not print "All the..."
Tiers 是 tuples,其中所有元素都是非 const 引用类型。它们通过调用 tie
函数模板(参见 make_tuple
)构造
int i; char c; double d; ... tie(i, c, a);
上面的 tie
函数创建一个类型为 tuple<int&, char&, double&>
的 tuple。使用调用 make_tuple(ref(i), ref(c), ref(a))
也可以实现相同的结果。
包含非 const 引用作为元素的 tuple 可用于将另一个 tuple“解包”到变量中。例如
int i; char c; double d; tie(i, c, d) = make_tuple(1,'a', 5.5); std::cout << i << " " << c << " " << d;
此代码将 1 a 5.5
打印到标准输出流。像这样的 tuple 解包操作可以在 ML 和 Python 中找到。当调用返回 tuples 的函数时,它很方便。
绑定机制也适用于 std::pair
模板
int i; char c; tie(i, c) = std::make_pair(1, 'a');
还有一个名为 ignore
的对象,允许您忽略由 tuple 赋值的元素。这个想法是,函数可能会返回一个 tuple,而您只对其中的一部分感兴趣。例如(请注意,ignore 在 tuples
子命名空间下)
char c; tie(tuples::ignore, c) = std::make_pair(1, 'a');
全局 operator<<
已针对 std::ostream
重载,以便通过为每个元素递归调用 operator<<
来输出 tuples。
类似地,全局 operator>>
已被重载,以通过为每个元素递归调用 operator>>
从 std::istream
中提取 tuples。
元素之间的默认分隔符是空格,tuple 用括号括起来。例如
tuple<float, int, std::string> a(1.0f, 2, std::string("Howdy folks!"); cout << a;
将 tuple 输出为:(1.0 2 Howdy folks!)
该库定义了三个操纵器来更改默认行为
set_open(char)
定义在第一个元素之前输出的字符。set_close(char)
定义在最后一个元素之后输出的字符。set_delimiter(char)
定义元素之间的分隔符字符。请注意,这些操纵器在 tuples 子命名空间中定义。例如
cout << tuples::set_open('[') << tuples::set_close(']') << tuples::set_delimiter(',') << a;
将相同的 tuple a
输出为:[1.0,2,Howdy folks!]
相同的操纵器也适用于 operator>>
和 istream
。假设 cin
流包含以下数据
(1 2 3) [4:5]
代码
tuple<int, int, int> i; tuple<int, int> j; cin >> i; cin >> tuples::set_open('[') >> tuples::set_close(']') >> tuples::set_delimiter(':'); cin >> j;
将数据读取到 tuples i
和 j
中。
请注意,通常情况下,提取具有 std::string
或 C 风格字符串元素的 tuples 不起作用,因为流式 tuple 表示可能无法明确解析。
所有 tuple 访问和构造函数都是小的内联单行代码。因此,与使用手写 tuple 类相比,一个不错的编译器可以消除使用 tuples 的任何额外成本。特别是,对于一个不错的编译器,以下代码之间没有性能差异
class hand_made_tuple { A a; B b; C c; public: hand_made_tuple(const A& aa, const B& bb, const C& cc) : a(aa), b(bb), c(cc) {}; A& getA() { return a; }; B& getB() { return b; }; C& getC() { return c; }; }; hand_made_tuple hmt(A(), B(), C()); hmt.getA(); hmt.getB(); hmt.getC();
和以下代码
tuple<A, B, C> t(A(), B(), C()); t.get<0>(); t.get<1>(); t.get<2>();
请注意,有一些广泛使用的编译器(例如 bcc 5.5.1)无法优化这种 tuple 用法。
根据编译器的优化能力,与使用非 const 引用参数作为从函数返回多个值的机制相比,tier 机制可能会有轻微的性能损失。例如,假设以下函数 f1
和 f2
具有等效的功能
void f1(int&, double&); tuple<int, double> f2();
然后,在下面的代码中,调用 #1 可能比 #2 稍快
int i; double d; ... f1(i,d); // #1 tie(i,d) = f2(); // #2
库代码是(?)标准 C++,因此该库适用于符合标准的编译器。以下是编译器列表以及每个编译器的已知问题
编译器 |
问题 |
---|---|
gcc 2.95 |
- |
edg 2.44 |
- |
Borland 5.5 |
不能使用函数指针或成员指针作为 tuple 元素 |
Metrowerks 6.2 |
不能使用 |
MS Visual C++ |
没有引用元素( |
Gary Powell 一直是不可或缺的帮手。特别是,用于 tuples 的流操纵器是他的想法。Doug Gregor 提出了一个适用于 MSVC 的工作版本,David Abrahams 找到了一种方法来消除大多数编译器不支持部分特化的限制。感谢 Jeremy Siek、William Kempf 和 Jens Maurer 的帮助和建议。Vesa Karvonen、John Max Skaller、Ed Brey、Beman Dawes、David Abrahams 和 Hartmut Kaiser 的评论帮助改进了该库。tie
机制的想法来自 Ian McCulloch 的一篇旧的 usenet 文章,他在文章中为 std::pair
提出了类似的东西。