boost::tuple 用法详解

2 篇文章 0 订阅

用法

Tuples 位于名字空间 tuples, 后者又位于名字空间 boost. 使用这个库要包含头文件 "boost/tuple/tuple.hpp"。关系操作符的定义在头文件"boost/tuple/tuple_comparison.hpp"中。tuples 的输入输出定义在头文件 "boost/tuple/tuple_io.hpp"中。tuple 的一些关键部件(tie 和 make_tuple)也可以直接在名字空间boost 中使用。在本节中,我们将讨论如何在一些常见情形下使用 tuples ,以及如何可以扩展这个库的功能以最好地符合我们的意图。我们将从构造 tuples 开始,并逐渐转移到其它主题,包括如何利用 tuples 的细节。

构造 Tuples

构造一个 tuple 包括声明各种类型,并可选地提供一组兼容类型的初始值。[1]

[1] 在特化时 tuple ,构造函数的参数不必与元素的类型精确相同,只要它们可以隐式地转换为元素的类型就可以了。

boost::tuple<int,double,std::string> 
triple(42,3.14,"My first tuple!");

类模板 tuple 模板参数指定了元素的类型。前面这个例子示范了一个带有三个类型的 tuple 的创建:一个 int, 一个 double, 和一个 std::string. 并向构造函数提供了三个参数来初始化所有三个元素的值。也可以传递少于元素数量的参数,那样的话剩下的元素将被缺省初始化。

boost::tuple<short,int,long> another;

在这个例子中,another 有类型为 shortint, 和 long 的元素,并且它们都被初始化为0.[2] 不管你的 tuple 是什么类型,这就是它如何定义和构造的方式。所以,如果你的 tuple 有一个元素类型不能缺省构造,你就需要自己初始化它。与定义 struct 相比,tuple 更容易声明、定义和使用。还有一个便于使用的函数,make_tuple,它使得创建 tuples 更加容易。它自动推断元素的类型,不用你来重复指定(这也会是出错的机会!)。

[2] 在一个模板上下文中,T() 对于一个内建类型而言意味着初始化为零。

boost::tuples::tuple<int,double> get_values() {
return boost::make_tuple(6,12.0);
}

函数 make_tuple 类似于 std::make_pair. 缺省情况下,make_tuple 设置元素类型为非const, 非引用的,即是最简单的、根本的参数类型。例如,考虑以下变量:

int plain=42;
int& ref=plain;
const int& cref=ref;

这三个变量根据它们的cv限定符(常量性)以及是否引用来命名。通过调用以下 make_tuple 创建的 tuple 都带有一个 int 元素。

boost::make_tuple(plain);
boost::make_tuple(ref);
boost::make_tuple(cref);

这种行为不总是正确的,但通常是,这正是为什么它是缺省行为的原因。为了使一个 tuple 的元素设为引用类型,你要使用函数 boost::ref, 它来自另一个名为 Boost.Ref 的 Boost 库。以下三行代码使用了我们前面定义的三个变量,但这次 tuple 带有一个 int& 元素,除了最后一个,它带的是一个 const int& 元素 (我们不能去掉 cref 的常量性):

boost::make_tuple(boost::ref(plain));
boost::make_tuple(boost::ref(ref));
boost::make_tuple(boost::ref(cref));

如果元素需要是 const 引用的,就使用来自 Boost.Ref 的 boost::cref。下面三个 tuples 带有一个 const int& 元素:

boost::make_tuple(boost::cref(plain));
boost::make_tuple(boost::cref(ref));
boost::make_tuple(boost::cref(cref));

ref 和 cref 在其它地方也经常使用。事实上,它们原先是作为 Boost.Tuple 库的一部分而建立的,但后来因为它们的通用性而移出去成为一个独立的库。

访问 tuple 元素

一个 tuple 的元素可以通过 tuple 成员函数 get 或普通函数 get 来访问。它们都要求用一个常量整型表达式来指定要取出的元素的索引。

#include <iostream>
#include <string>

#include "boost/tuple/tuple.hpp"

int main() {
boost::tuple<int,double,std::string>
triple(42,3.14,"The amazing tuple!");

int i=boost::tuples::get<0>(triple);
double d=triple.get<1>();
std::string s=boost::get<2>(triple);
}

这个例子中,一个三元素的 tuple 取名为 triple 。triple 含有一个 int, 一个 double, 和一个 string, 它们可以用 get 函数取出。

int i=boost::tuples::get<0>(triple);

这里,你看到的是普通函数 get 。它把 tuple 作为一个参数。注意,给出一个无效的索引会导致一个编译期错误。这个函数的前提是索引值对于给定的 tuple 类型必须有效。

double d=triple.get<1>();

这段代码使用的是成员函数 get. 它也可以写成这样:

double& d=triple.get<1>();

这个绑定到一个引用的方式可以使用,因为 get 总是返回一个到元素的引用。如果 tuple, 或者其类型,是 const 的,则返回一个 const 引用。这两个函数是等价的,但在某些编译器上,只有普通函数可以正确工作。普通函数有一个优点,它提供了与 tuple 之外的其它类型一致的提取元素的风格。通过索引来访问 tuple 的元素而不是通过名字来访问,这样做的一个优点是它可以支持泛型的解决方法,因为这样做不依赖于某个特定的名字,仅仅是一个索引值。稍后对此有更多介绍。

Tuple 赋值及复制构造

tuples 可以被赋值和被复制构造,可以在两个 tuple 间进行,只要它们的元素类型可以相互转换。要赋值或复制 tuples, 就是执行成员间的赋值或复制,因此这两个 tuples 必须具有相同数量的元素。源 tuple 的元素必须可以转换为目标 tuple 的元素。以下例子示范了如何使用。

#include <iostream>
#include <string>

#include "boost/tuple/tuple.hpp"

class base {
public:
virtual ~base() {};
virtual void test() {
std::cout << "base::test()\n";
}
};

class derived : public base {
public:
virtual void test() {
std::cout << "derived::test()\n";
}
};

int main() {
boost::tuple<int,std::string,derived> tup1(-5,"Tuples");
boost::tuple<unsigned int,std::string,base> tup2;
tup2=tup1;

tup2.get<2>().test();
std::cout << "Interesting value: "
<< tup2.get<0>() << '\n';

const boost::tuple<double,std::string,base> tup3(tup2);
tup3.get<0>()=3.14;
}

这个例子开始时定义两个类,base 和 derived, 它们被用作两个 tuple 类型的元素。第一个 tuple 有三个元素,类型为 intstd::string, 和 derived. 第二个 tuple 有三个兼容类型的元素,分别为 intstd::string, 和 base. 因此,这两个 tuples 符合赋值的要求,这就是为什么 tup2=tup1 有效的原因。在这个赋值中,tup1 的第三个元素类型为 derived, 被赋值给 tup2的第三个元素,后者类型为 base. 赋值可以成功,但 derived 对象被切割,因此这样会破坏多态性。

tup2.get<2>().test();

这一行取出一个 base&, 但 tup2 中的对象类型为 base, 因此它将调用 base::test. 我们可以通过把 tuples 修改为分别包含 base 和 derived 的引用或指针来获得多态行为。注意数值转换的危害(精度损失、正负溢出)也会出现在 tuples 间的转换。这种危险的转换可以通过 Boost.Conversion 库的帮助来变得安全,请参见"Library 2Conversion"。

下一行是复制构造一个新的 tupletup3, 它的类型不同但还是兼容于 tup2.

const boost::tuple<double,std::string,base> tup3(tup2);

注意,tup3 被声明为 const. 这意味着本例中有一个错误。看你能否找到它。我等一下你...,你看出来了吗?就是这里:

tup3.get<0>()=3.14;

因为 tup3 是 constget 将返回一个 const double&. 这意味着这个赋值语句是非法的,这个例子不能编译。tuples 间的赋值与复制构造是符合直觉的,因为它的语义与单个元素是相同的。通过下面这个例子,我们来看看如何在 tuples 间的派生类至基类的赋值中获得多态行为。

derived d;
boost::tuple<int,std::string,derived*>
tup4(-5,"Tuples",&d);
boost::tuple<unsigned int,std::string,base*> tup5;
tup5=tup4;

tup5.get<2>()->test();

boost::tuple<int,std::string,derived&>
tup6(12,"Example",d);

boost::tuple<unsigned int,std::string,base&> tup7(tup6);

tup7.get<2>()->test();

在这两种情况下,都会调用 derived::test ,这正是我们想要的。tup6 和 tup7 间不能赋值,因为你不能对一个引用进行赋值,这就是为什么 tup7 要从 tup6 复制构造,以及 tup6 要用 d 进行初始化的原因。因为 tup4 和 tup5 对它们的第三个元素使用的是指针,因此它们可以支持赋值。注意,通常在 tuple 中最好使用智能指针(与裸指针相比),因为它们可以减轻对指针所指向的资源的生存期管理的压力。但是,正如 tup4 和 tup5 所示,在 tuples 中,指针不总是指向需要进行内存管理的东西的。(参考 "Library 1Smart_ptr 1",可获得 Boost 强大的智能指针的更多信息)

比较 Tuples

要比较 tuples, 你必须包含头文件 "boost/tuple/tuple_comparison.hpp"tuple 的关系操作符有 ==,!=,<,>,<= 和 >=, 它们按顺序地对要比较的 tuples 中的每一对元素调用相应的操作符。这些比较是短路的,即只比较到可以得到正确结果为止。只有具有相同数量元素的 tuples 可以进行比较,并且显然两个 tuples 的对应的元素必须是可比较的。如果两个 tuples 的所有元素对都相等,则相等比较返回 true 。如果任意一对元素的相等比较返回 falseoperator== 也将返回 false。不等比较也是类似的,但返回的是相反的结果。其它关系操作符按字典序进行比较。

以下例程示范比较操作符的行为。

#include <iostream>
#include <string>

#include "boost/tuple/tuple.hpp"
#include "boost/tuple/tuple_comparison.hpp"

int main() {
boost::tuple<int,std::string> tup1(11,"Match?");
boost::tuple<short,std::string> tup2(12,"Match?");

std::cout << std::boolalpha;

std::cout << "Comparison: tup1 is less than tup2\n";

std::cout << "tup1==tup2: " << (tup1==tup2) << '\n';
std::cout << "tup1!=tup2: " << (tup1!=tup2) << '\n';
std::cout << "tup1<tup2: " << (tup1<tup2) << '\n';
std::cout << "tup1>tup2: " << (tup1>tup2) << '\n';
std::cout << "tup1<=tup2: " << (tup1<=tup2) << '\n';
std::cout << "tup1>=tup2: " << (tup1>=tup2) << '\n';

tup2.get<0>()=boost::get<0>(tup1); //tup2=tup1 also works

std::cout << "\nComparison: tup1 equals tup2\n";

std::cout << "tup1==tup2: " << (tup1==tup2) << '\n';
std::cout << "tup1!=tup2: " << (tup1!=tup2) << '\n';
std::cout << "tup1<tup2: " << (tup1<tup2) << '\n';
std::cout << "tup1>tup2: " << (tup1>tup2) << '\n';
std::cout << "tup1<=tup2: " << (tup1<=tup2) << '\n';
std::cout << "tup1>=tup2: " << (tup1>=tup2) << '\n';
}

如你所见,这两个 tuples, tup1 和 tup2, 并不是严格相同的类型,但它们的类型是可比较的。在第一组比较中,tuples 的第一个元素值不同,而在第二组中,tuples 是相同的。以下是程序的运行输出。

Comparison: tup1 is less than tup2
tup1==tup2: false
tup1!=tup2: true
tup1<tup2: true
tup1>tup2: false
tup1<=tup2: true
tup1>=tup2: false

Comparison: tup1 equals tup2
tup1==tup2: true
tup1!=tup2: false
tup1<tup2: false
tup1>tup2: false
tup1<=tup2: true
tup1>=tup2: true

支持比较的一个重要方面是,tuples 可以被排序,这意味着它们可以在关联容器中被排序。有些时候,我们需要按 tuple 中的某一个元素进行排序(建立一个弱序),我们可以用一个简单的泛型方法来实现。

template <int Index> class element_less {
public:
template <typename Tuple>
bool operator()(const Tuple& lhs,const Tuple& rhs) const {
return boost::get<Index>(lhs)<boost::get<Index>(rhs);
}
};

这显示了使用索引而不是用名字来访问元素的优势;它可以很容易地创建泛型的构造来执行强大的操作。我们的 element_less 可以这样用:

#include <iostream>
#include <vector>
#include "boost/tuple/tuple.hpp"
#include "boost/tuple/tuple_comparison.hpp"

template <int Index> class element_less {
public:
template <typename Tuple>
bool operator()(const Tuple& lhs,const Tuple& rhs) const {
return boost::get<Index>(lhs)<boost::get<Index>(rhs);
}
};


int main() {
typedef boost::tuple<short,int,long,float,double,long double>
num_tuple;

std::vector<num_tuple> vec;

vec.push_back(num_tuple(6,2));
vec.push_back(num_tuple(7,1));
vec.push_back(num_tuple(5));

std::sort(vec.begin(),vec.end(),element_less<1>());

std::cout << "After sorting: " <<
vec[0].get<0>() << '\n' <<
vec[1].get<0>() << '\n' <<
vec[2].get<0>() << '\n';
}

vec 由三个元素组成。使用从我们前面创建的模板所特化的 element_less<1> 函数对象,来执行基于 tuples 的第二个元素的排序。这类函数对象还有更多的应用,如用于查找指定的 tuple 元素。

绑定 Tuple 元素到变量

Boost.Tuple 库的一个方便的特性是"绑定" tuples 到变量。绑定者就是用重载函数模板 boost::tie 所创建的 tuples,它的所有元素都是非const 引用类型。因此,ties 必须使用左值进行初始化,从而 tie 的参数也必须是非 const 引用类型。由于结果 tuples 具有非 const 引用类型,对这样一个 tuple 的元素进行赋值,就会通过非 const 引用赋值给调用 tie 时的左值。这样就绑定了一个已有变量给 tuple, tie 的名字由此而来!

以下例子首先示范了一个通过返回 tuple 获得值的明显的方法。然后,它示范了通过一个 tietuple 直接赋值给变量以完成相同操作的方法。为了让这个例子更为有趣,我们开始时定义了一个返回两个数值的最大公约数和最小公倍数的函数。当然,这两个结果值被组合成一个 tuple 返回类型。你将发现计算最大公约数和最小公倍数的函数来自于另一个 Boost 库——Boost.Math.

#include <iostream>
#include "boost/tuple/tuple.hpp"
#include "boost/math/common_factor.hpp"

boost::tuple<int,int> gcd_lcm(int val1,int val2) {
return boost::make_tuple(
boost::math::gcd(val1,val2),
boost::math::lcm(val1,val2));
}

int main() {
//"老"方法
boost::tuple<int,int> tup;
tup=gcd_lcm(12,18);
int gcd=tup.get<0>(); // 译注:原文为 int gcd=tup.get<0>()); 明显有误
int lcm=tup.get<1>(); // 译注:原文为 int gcd=tup.get<1>()); 明显有误

std::cout << "Greatest common divisor: " << gcd << '\n';
std::cout << "Least common multiple: " << lcm << '\n';

//"新"方法
boost::tie(gcd,lcm)=gcd_lcm(15,20);

std::cout << "Greatest common divisor: " << gcd << '\n';
std::cout << "Least common multiple: " << lcm << '\n';
}

有时我们并不是对返回的 tuple 中所有的元素感兴趣,tie 也可以支持这种情况。有一个特殊的对象,boost:: tuples::ignore,它忽略一个 tuple 元素的值。如果前例中我们只对最大公约数感兴趣,我们可以这样写:

boost::tie(gcd,boost::tuples::ignore)=gcd_lcm(15,20);

另一种方法是创建一个变量,传递给 tie, 然后在后面的处理中忽略它。这样做会令维护人员弄不清楚这个变量为什么存在。使用 ignore 可以清楚地表明代码将不使用 tuple 的那个值。

注意,tie 也支持 std::pair. 用法与从 boost::tuples 绑定值一样。

std::pair<short,double> p(3,0.141592);
short s;
double d;

boost::tie(s,d)=p;

绑定 tuples 不仅仅是方便使用;它有助于使代码更为清晰。

Tuples的流操作

在本章的每一个例子中,取出 tuples 的元素都只是为了能够把它们输出到 std::cout. 可以象前面那样做,但还有更容易的方法。tuple 库支持输入和输出流操作;tuple 重载了operator>>和 operator<<。还有一些操纵器用于改变输入输出的缺省分隔符。对输入操作改变分隔符改变 operator>> 查找元素值的结果。我们用一个简单的读写 tuples 的程序来测试一下这些情况。注意,要使用 tuple 的流操作,你必须包含头文件 "boost/tuple/tuple_io.hpp".

#include <iostream>
#include "boost/tuple/tuple.hpp"
#include "boost/tuple/tuple_io.hpp"

int main() {
boost::tuple<int,double> tup1;
boost::tuple<long,long,long> tup2;

std::cout << "Enter an int and a double as (1 2.3):\n";
std::cin >> tup1;

std::cout << "Enter three ints as |1.2.3|:\n";
std::cin >> boost::tuples::set_open('|') >>
boost::tuples::set_close('|') >>
boost::tuples::set_delimiter('.') >> tup2;

std::cout << "Here they are:\n"
<< tup1 << '\n'
<< boost::tuples::set_open('\"') <<
boost::tuples::set_close('\"') <<
boost::tuples::set_delimiter('-');

std::cout << tup2 << '\n';
}

上面这个例子示范了如何对 tuples 使用流操作符。tuples 的缺省分隔符是:( (左括号) 作为开始分隔符,) (右括号) 作为结束分隔符,空格用于分隔各个 tuple 元素值。这意味着我们的程序要正确运行的话,我们需要象这样输入:(12 54.1) 和 |4.5.3|. 以下是运行的例子。

Enter an int and a double as (1 2.3):
(12 54.1)
Enter three ints as |1.2.3|:
|4.5.3|
Here they are:
(12 54.1)
"4-5-3"

对流操作的支持是很方便的,通过对分隔符操纵器的支持,可以很容易让使用 tuple 的代码的流操作兼容于已有代码。

关于 Tuples 的更多

还有很多我们没有看到的工具可用于 tuples。这些更为先进的特性对于创建使用 tuple 的泛型结构至为重要。例如,你可以获得一个 tuple 的长度(即元素的数量),取出某个元素的类型,使用null_type tuple 哨兵来终结递归模板实例化。

不可能用一个 for 循环来迭代一个 tuple 里的元素,因为 get 要求提供一个常量整型表达式。但是,使用模板元编程,我们可以打印一个 tuple 的所有元素。

#include <iostream>
#include <string>
#include "boost/tuple/tuple.hpp"

template <typename Tuple,int Index> struct print_helper {
static void print(const Tuple& t) {
std::cout << boost::tuples::get<Index>(t) << '\n';
print_helper<Tuple,Index-1>::print(t);
}
};

template<typename Tuple> struct print_helper<Tuple,0> {
static void print(const Tuple& t) {
std::cout << boost::tuples::get<0>(t) << '\n';
}
};

template <typename Tuple> void print_all(const Tuple& t) {
print_helper<
Tuple,boost::tuples::length<Tuple>::value-1>::print(t);
}

int main() {
boost::tuple<int,std::string,double>
tup(42,"A four and a two",42.424242);

print_all(tup);
}

在这个例子中有一个辅助类模板,print_helper, 它是一个元程序,访问 tuple 的所有索引,并对每个索引打印出相应元素。偏特化版本用于结束模板递归。函数 print_all 用它的 tuple 参数的长度以及这个 tuple 来调用 print_helper 构造函数。tuple 的长度可以这样来取得:

boost::tuples::length<Tuple>::value

这是一个常量整型表达式,这意味着它可以作为第二个模板参数传递给 print_helper. 但是,我们的解决方案中有一个警告,我们看看这个程序的输出结果就清楚了。

42.4242
A four and a two
42

我们按反序来打印元素了!虽然有些情况下可能会需要这种用法(他狡猾地辩解说),但在这里不是。问题在于 print_helper 先打印 boost::tuples::length<Tuple>::value-1 元素的值,然后才到前一个元素,直到偏特化版本打印第一个元素值为止。我们不应该使用第一个元素作为特化条件以及从最后一个元素开始,而是需要从第一个元素开始以及使用最后一个元素作为特化条件。这怎么可能?在你明白到 tuple 是以一个特殊的类型 boost::tuples:: null_type 作为结束以后,解决的方法就很明显了。我们可以确保一个 tuple 中的最后一个类型是 null_type, 这也意味着我们的解决方法应该是对 null_type 进行特化或函数重载。

剩下的问题就是取出第一个元素的值,然后继续,并在列表尾部结束。tuples 提供了成员函数 get_head 和 get_tail 来访问其中的元素。顾名思义,get_head 返回数值序列的头,即第一个元素的值。get_tail 返回一个由该 tuple 中除了第一个值以外的其它值组成的 tuple. 这就引出了 如下 print_all 的解决方案。

void print_all(const boost::tuples::null_type&) {}

template <typename Tuple> void print_all(const Tuple& t) {
std::cout << t.get_head() << '\n';
print_all(t.get_tail());
}

这个解决方案比原先的更短,并且按正确的顺序打印元素的数值。每一次函数模板 print_all 执行时,它打印 tuple 的第一个元素,然后用一个由 t 中除第一个值以外的其它值所组成的 tuple递归调用它自己。当 tuple 没有数值时,结尾是一个 null_type, 将调用重载函数 print_all ,递归结束。

可以知道某个元素的类型有时是有用的,例如当你要在泛型代码中声明一个由 tuple 的元素初始化的变量时。考虑一个返回 tuple 中前两个元素的和的函数,它有一个额外的要求,即返回值的类型必须是两个中较大的那个类型(例如,考虑整数类型)。如果不清楚元素的类型,就不可能创建一个通用的解决方案。这正是辅助模板 element<N,Tuple>::type 要做的,如下例所示。我们所面对的问题不仅仅是计算哪个元素具有较大的类型,还有如何声明这个函数的返回值类型。这有点复杂,但我们可以通过增加一个间接层来解决。这个间接层以一个额外的辅助模板形式出现,它有一个责任:提供一个 typedef 以定义两种类型中的较大者。代码可能看起来有点多,但它的确可以完成任务。

#include <iostream>
#include "boost/tuple/tuple.hpp"
#include <cassert>
#include <typeinfo> //译注:原文没有这行,不能通过编译

template <bool B,typename Tuple> struct largest_type_helper {
typedef typename boost::tuples::element<1,Tuple>::type type;
};

template<typename Tuple> struct largest_type_helper<true,Tuple> {
typedef typename boost::tuples::element<0,Tuple>::type type;
};

template<typename Tuple> struct largest_type {
typedef typename largest_type_helper<
(sizeof(boost::tuples::element<0,Tuple>)>
sizeof(boost::tuples::element<1,Tuple>)),Tuple>::type type;
};

template <typename Tuple> typename largest_type<Tuple>::type sum(const Tuple& t) { typename largest_type<Tuple>::type    result=boost::tuples::get<0>(t)+      boost::tuples::get<1>(t); return result;}int main() { typedef boost::tuple<short,int,long> my_tuple; boost::tuples::element<0,my_tuple>::type first=14; assert(typeid(first) == typeid(short)); //译注:原文为assert(type_id(first) == typeid(short)); 明显有误 boost::tuples::element<1,my_tuple>::type second=27; assert(typeid(second) == typeid(int)); //译注:原文为assert(type_id(second) == typeid(int)); 明显有误 boost::tuples::element<    boost::tuples::length<my_tuple>::value-1,my_tuple>::type     last; my_tuple t(first,second,last); std::cout << "Type is int? " <<     (typeid(int)==typeid(largest_type<my_tuple>::type)) << '\n'; int s=sum(t);}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值