8.4 使用方法
元组位于命名空间tuples中,而后者位于命名空间boost中。为了使用这个库,程序中需要包含头文件“boost/tuple/tuple.hpp”。关系运算符定义在头文件“boost/tuple/tuple_comparison.hpp”中。元组的输入和输出操作定义在头文件“boost/tuple/tuple_io.hpp”中。一些tuple关键组件(tie和make_tuple)同样可以在命名空间boost中直接使用。在本节中,将讨论如何在一些典型情形中使用元组,以及如何扩展这个库的功能以最好地符合我们的意图。我们将从元组的构造开始,并逐渐转移到其他主题上,其中包括如何利用元组的细节。
8.4.1 元组的构造
构造tuple包括声明类型,以及提供一组与声明类型兼容的初始值(可选)。
boost::tuple<int,double,std::string>
triple(42,3.14,"My first tuple!");
类模板tuple的模板参数指定了元素的类型。上述例子示范了如何用3个类型创建tuple,其中这3个类型分别为:int、double和std::string。为构造函数提供3个参数就可以初始化所有3个元素的值。另外,传递的参数数量也有可能少于元素的数量,那样的话将致使剩余的元素被默认初始化。
boost::tuple<short,int,long> another;
在这个例子中,another拥有类型为short、int和long的元素,而且将它们都初始化为0。这就是tuple定义和构造的方式,与tuple的类型集无关。因此,如果tuple的某个元素类型不是默认构造的,那么大家就需要自己来初始化它。与定义struct相比,tuple的声明、定义和使用更加简单。另外还有一个便于使用的函数make_tuple,它使tuple的创建更加容易。该函数可以自动地推断元素的类型,不用大家再单调乏味地显式指定它们(这也容易产生错误!)。
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;
这3个变量根据它们的cv限定词(常量性)以及是否是引用来进行命名。通过对下面的make_tuple函数的调用,所创建的tuple都将拥有一个int类型的元素。
boost::make_tuple(plain);
boost::make_tuple(ref);
boost::make_tuple(cref);
这种行为不总是正确的,但大多数情况下是正确的,这正是为什么它是默认行为的原因。为了使tuple的元素成为引用类型,请使用函数boost::ref,该函数来自另一个Boost库Boost.Ref。下面的3行代码使用了前面定义的3个变量,但是这一次,除了最后一个tuple外,另两个tuple都拥有一个int&元素,最后一个tuple所拥有的是一个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。下面的3个元组都拥有一个const int&元素:
boost::make_tuple(boost::cref(plain));
boost::make_tuple(boost::cref(ref));
boost::make_tuple(boost::cref(cref));
在这里使用ref和cref可能显而易见,但是ref和cref在其他上下文中也经常使用。事实上,它们原先是作为Boost.Tuple库的一部分建立的,但是后来因为它们的通用性,就将它们移出去成为一个独立的库。
8.4.2 访问tuple的元素
tuple的元素可以通过tuple的成员函数get或者自由函数get来访问。这两个函数都要求使用一个常整型表达式(constant integral expression)来指示被检索元素的索引。
#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);
}
这个例子创建了一个拥有3个元素的tuple,并将它创造性地命名为triple。triple包含了3个类型分别为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的,那么get函数返回的就是一个const引用。这两个函数是等价的,但在某些编译器上,只有自由函数可以正确工作。自由函数有一个优点,即它提供了与tuple之外的其他类型一致的元素提取风格。通过索引访问tuple的元素而不是通过名称来访问,这样做的一个优点就是它可以支持泛型的解决方法,因为这样做就不必再依赖于某个特定的名称,只需一个索引值即可。稍后还会对此进行更详细地介绍。
8.4.3 tuple的赋值和复制构造
tuple可以被赋值和复制构造,只要两个tuple之间的元素类型可以相互转换即可。为了赋值或者复制tuple,就需要执行成员之间的赋值或复制,因此这两个tuple必须具有相同数量的元素。源tuple的元素必须能转换为目标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包含3个元素,类型分别为:int、std::string和derived。第二个tuple包含3个兼容类型的元素,类型分别为:无符号int、std::string和base。因此,这两个tuple满足赋值的要求,这就是tup2=tup1之所以有效的原因。在这个赋值中,tup1的第三个元素类型为derived,被赋值给tup2的第三个元素,后者类型为base。最终赋值成功,但是却将derived对象切割了,从而破坏了多态性。
tup2.get<2>().test();
这一行代码要提取base&,但是tup2中的对象的类型为base,因此它最终将调用base::test。可以通过将两个tuple分别修改为包含base和derived的引用或者指针,从而使行为真正多态。这里需要注意数值转换的危害(精度损失、正负溢出)也会出现在tuple间的转换中。这些危险的转换可以通过Boost.Conversion库的帮助变得安全,详细内容请参见第2章“Conversion库”。
例子中的下一行代码复制构造了一个新的tuple:tup3,它的类型不同,但仍与tup2中的类型兼容。
const boost::tuple<double,std::string,base> tup3(tup2);
这里需要注意的是,此处将tup3声明为const。这意味着这个例子中存在一个错误。看看大家能否找到它。我等大家一会儿……大家看出来了吗?就是这里:
tup3.get<0>()=3.14;
这是因为tup3是const的,而get返回的是一个const double&。这意味着这个赋值语句是非法的,这个例子无法通过编译。tuple间的赋值和复制构造符合直观感觉,因为tuple的语义与单个元素是完全相同的。下面通过例子来描述如何在tuple间的derived类到base类的赋值中获得多态行为。
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所示,在tuple中,指针并不总是引用需要进行内存管理的对象。(关于Boost中强大的智能指针的更多信息,请参见第1章“Smart_ptr库”。)
8.4.4 tuple的比较
为了比较tuple,程序中需要包含头文件“boost/tuple/tuple_comparison.hpp”。tuple的关系运算符有==、!=、<、>、<=和>=,它们对要进行比较的tuple中的每一对元素依次调用同一个运算符。这些成双成对的比较是简化的,这意味着我们只需比较到可以得到正确的结果即可。只有具有相同数量的元素的tuple才可以进行比较,并且显然两个tuple的对应元素必须是可比较的。如果两个tuple的所有元素对都是相等的,那么相等比较才能返回true。如果元素对之间的任何一个相等比较返回false,那么operator==也将返回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';
}
正如大家所见,这两个tuple:tup1和tup2,并不具有完全相同的类型,但是它们的类型是可比较的。在第一组比较中,tuple的第一个元素拥有不同的值,而在第二组比较中,tuple是相等的。运行上述程序可以获得如下的输出结果。
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
支持比较的一个重要方面就是tuple可以进行排序,这意味着它们可以存储在关联容器中。有些时候,我们需要根据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由3个元素组成。对tuple的第二个元素所执行的排序使用了前面所创建的模板中的函数对象element_less<1>。这类函数对象还有更多的应用,例如查找特定的tuple元素。
8.4.5 将tuple的元素绑定到变量
Boost.Tuple库的一个便利特性就是可以将tuple“绑定”到变量。绑定者就是重载的函数模板boost::tie所创建的tuple,它的所有元素都是非const的引用类型。因此,必须用左值(lvalue)初始化tie,从而tie的参数也必须是非const的引用类型。因为最后生成的tuple拥有非const的引用元素,所以对这样的tuple的元素进行的任何赋值,都会通过非const的引用赋值给调用tie的左值。这样就将已有的变量绑定到tuple,tie的名称也是由此而来!
下面的例子首先示范了一个从返回的tuple获取值的明显的方法。然后,该例又示范了另一个方法来完成相同的操作,即使用一个绑定的tuple直接将值赋值给变量。为了让这个例子更加吸引人,我们开始时定义了一个返回两个值的最大公约数和最小公倍数的函数。当然,这两个结果值被组合成一个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() {
//The "old" way
boost::tuple<int,int> tup;
tup=gcd_lcm(12,18);
int gcd=tup.get<0>());
int lcm=tup.get<1>());
std::cout << "Greatest common divisor: " << gcd << '\n';
std::cout << "Least common multiple: " << lcm << '\n';
//The "new" way
boost::tie(gcd,lcm)=gcd_lcm(15,20);
std::cout << "Greatest common divisor: " << gcd << '\n';
std::cout << "Least common multiple: " << lcm << '\n';
}
有些情况下,我们并不会对返回的元组中的所有元素感兴趣,tie也适用于这种情况。Tuple库中有一个特殊的对象—— boost::tuples::ignore—— 它可以忽略一个tuple元素的值。如果在上述例子中只对最大公约数感兴趣,那么可以按下述方式表示:
boost::tie(gcd,boost::tuples::ignore)=gcd_lcm(15,20);
另一种方法就是创建一个变量,传递给tie,然后在当前作用域的其余部分忽略它。这样做会使维护人员对这个变量的存在产生怀疑。通过使用ignore可以清楚地表明代码将不再使用tuple的那个值。
这里需要注意的是,tie也适用于std::pair。它的用法与绑定boost::tuple中的值一样。
std::pair<short,double> p(3,0.141592);
short s;
double d;
boost::tie(s,d)=p;
绑定tuple不仅方便使用,而且还可以使代码更为清晰。
8.4.6 tuple的流操作
在本章的每一个例子中,提取tuple的元素都只是为了能够把它们流输出到std::cout中。尽管可以像前面那样做,但是还有一个更容易的方法。tuple库支持输入和输出流操作,tuple重载了operator>>和operator<<。tuple库还有一些操纵器(manipulator)用于改变输入和输出流操作的默认分隔符。通过修改输入的分隔符可以改变operator>>查找元素值的结果。下面用一个简单的读写tuple的程序来测试一下这些情况。但是请注意,为了使用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';
}
上述例子示范了如何对tuple使用流操作运算符。tuple的默认分隔符是:“(”(左括号)作为开始分隔符(opening delimiter),“)”(右括号)作为结束分隔符(closing delimiter),空格用于分隔各个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的遗留代码兼容。
8.4.7 关于tuple的更多知识
tuple还有很多工具我们还没有看到。这些更高级的特性对于创建使用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的所有索引,输出每个索引所对应的元素。而部分特化(partial specialization)用来结束模板的递归。函数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进行特化或者函数重载。
剩下的问题就是获取第一个元素的值,然后获取第二个元素的值,依此类推,最终在列表尾部结束。tuple提供了成员函数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>
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));
boost::tuples::element<1,my_tuple>::type second=27;
assert(typeid(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);
}
如果大家还不太清楚模板元编程的运用,那么也不用担心—— 对于Tuple库的使用这并不是必需的。虽然这类编程方式需要花费一段时间才能习惯,但是它的思想还是相当简单的。largest_type从两个辅助类模板largest_type_helper中的一个获得typedef,具体使用哪一个模板就依赖于哪个的布尔参数被部分特化了。这个参数是通过比较tuple(第二个模板参数)的前两个元素的大小来决定的。这样的结果是typedef会表现为两个类型中较大的那一个。上述程序中的函数sum使用这个类型作为返回值的类型,剩下的部分就是简单地对两个元素进行相加。
这个例子的剩余部分示范了如何使用函数sum,同样还示范了如何从特定的tuple的元素的类型来声明变量。这个例子中的前两个变量在tuple中使用了硬编码的索引(hardcoded index)。
boost::tuples::element<0,my_tuple>::type first=14;
boost::tuples::element<1,my_tuple>::type second=27;
最后一个声明检索tuple的最后一个元素的索引,并用它作为辅助模板的输入来(泛型地)声明这个类型。
boost::tuples::element<
boost::tuples::length<my_tuple>::value-1,my_tuple>::type last;
8.4.8 tuple和for_each
前面用于创建print_all函数的方法可以推广至创建像std::for_each那样的更为通用的机制。例如,如果不想输出元素,而是想对它们取和或者复制它们,或者只想输出它们中的一部分,那该怎么办呢?对tuple的元素进行顺序访问并不简单,正如我们在开发前面的例子时所发现的一样。创建一个通用的解决方案来接受一个函数或函数对象的参数来调用tuple的元素是很有意义的。这样就不仅可以实现(有点限制的)print_all函数的功能,而且还可以执行任何函数,只要这些函数可以接受tuple中的元素的类型。下面的例子创建了一个名为for_each_element的函数模板来实现这个功能。这个例子用两个函数对象作为参数来示范for_each_element的工作机制。
#include <iostream>
#include <string>
#include <functional>
#include "boost/tuple/tuple.hpp"
template <typename Function> void for_each_element(
const boost::tuples::null_type&, Function) {}
template <typename Tuple, typename Function> void
for_each_element(Tuple& t, Function func) {
func(t.get_head());
for_each_element(t.get_tail(),func);
}
struct print {
template <typename T> void operator()(const T& t) {
std::cout << t << '\n';
}
};
template <typename T> struct print_type {
void operator()(const T& t) {
std::cout << t << '\n';
}
template <typename U> void operator()(const U& u) {}
};
int main() {
typedef boost::tuple<short,int,long> my_tuple;
boost::tuple<int,short,double> nums(1,2,3.01);
for_each_element(nums, print());
for_each_element(nums, print_type<double>());
}
函数for_each_element通过重载使之可以接受类型为null_type的参数,重用了前面例子中的策略,重载的函数可以通知tuple的元素已到达结尾,从而不再执行任何操作。下面介绍完成实际工作的那个函数。
template <typename Tuple, typename Function> void
for_each_element(Tuple& t, Function func) {
func(t.get_head());
for_each_element(t.get_tail(),func);
}
第二个模板和函数参数指定了以tuple的元素作为参数进行调用的函数(或函数对象)。for_each_element首先使用get_head返回的元素来调用函数(对象)。这里需要注意的是,get_head返回的是tuple的当前元素。然后,它用tuple的尾部或者剩余元素递归地调用自身。第二次调用同样提取出头一个元素并用它调用函数(对象),然后再次递归,依此类推。最后,get_tail发现tuple中没有元素了,就返回一个null_type的实例,它通过匹配非递归的for_each_element的重载版本,从而结束了递归。这就是for_each_element的工作机制!
接下来,这个例子给出两个示例函数对象,它们所使用的技术可以在其他上下文中重用。其中一个是print函数对象。
struct print {
template <typename T> void operator()(const T& t) {
std::cout << t << '\n';
}
};
这个print函数对象没有什么奇怪的地方,但是最终的结果是,许多程序员还没有意识到函数调用运算符可以是模板的!通常情况下,函数对象可以对它所使用的一个或多个类型进行参数化,但是对于tuple却不适用,这是因为tuple的元素通常是完全不同的类型。因此,不能对函数对象本身进行参数化,而是对函数调用运算符进行参数化,这样做的另一个好处就是它的使用更加简单,如下所示。
for_each_element(nums, print());
这里不需要指定类型,但是参数化的函数对象则需要。有些时候,把模板参数提供给类的成员函数是很有用的,通常对用户比较友好。
第二个函数对象输出特定类型的所有元素。这种类型的过滤方法也可以用于提取兼容类型的元素。
template <typename T> struct print_type {
void operator()(const T& t) {
std::cout << t << '\n';
}
template <typename U> void operator()(const U& u) {}
};
这个函数对象显示了另一个有用的技术,我喜欢把它称为忽略重载(discarding overload)。它用于忽略传递给它的除了类型T提及的元素以外的所有元素。这里的窍门就是除了指定的类型之外,为其他类型重载一个更好的匹配。这可能会让大家联想到与这种技术密切相关的技术:即sizeof窍门和省略号(...)结构,其中后者也可以用于在编译时作决定,但是它不能在这里使用,这是因为这里确实调用了函数,但是却没有做任何事情。这个函数对象可以按如下方式使用:
for_each_element(print_type<double>(),nums);
Tuple库是不是很容易使用,而且代码也很容易编写,Tuple库还有更多的价值。函数对象可能没有Tuple库这么多的特性,可以使用这些技术或者其他的惯用法。






