## 用法
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` 有類型為 `short`, `int`, 和 `long` 的元素,并且它們都被初始化為0.\[2\] 不管你的 `tuple` 是什么類型,這就是它如何定義和構造的方式。所以,如果你的 `tuple` 有一個元素類型不能缺省構造,你就需要自己初始化它。與定義 `struct` 相比,`tuple` 更容易聲明、定義和使用。還有一個便于使用的函數,`make_tuple`,它使得創建 `tuple`s 更加容易。它自動推斷元素的類型,不用你來重復指定(這也會是出錯的機會!)。
> \[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 賦值及復制構造
`tuple`s 可以被賦值和被復制構造,可以在兩個 `tuple` 間進行,只要它們的元素類型可以相互轉換。要賦值或復制 `tuple`s, 就是執行成員間的賦值或復制,因此這兩個 `tuple`s 必須具有相同數量的元素。源 `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` 有三個元素,類型為 `int`, `std::string`, 和 `derived`. 第二個 `tuple` 有三個兼容類型的元素,分別為 `int`, `std::string`, 和 `base`. 因此,這兩個 `tuple`s 符合賦值的要求,這就是為什么 `tup2=tup1` 有效的原因。在這個賦值中,`tup1` 的第三個元素類型為 `derived`, 被賦值給 `tup2` 的第三個元素,后者類型為 `base`. 賦值可以成功,但 `derived` 對象被切割,因此這樣會破壞多態性。
```
tup2.get<2>().test();
```
這一行取出一個 `base&`, 但 `tup2` 中的對象類型為 `base`, 因此它將調用 `base::test`. 我們可以通過把 `tuple`s 修改為分別包含 `base` 和 `derived` 的引用或指針來獲得多態行為。注意數值轉換的危害(精度損失、正負溢出)也會出現在 `tuple`s 間的轉換。這種危險的轉換可以通過 Boost.Conversion 庫的幫助來變得安全,請參見"[Library 2](../Text/content.html#ch02): [Conversion](../Text/content.html#ch02)"。
下一行是復制構造一個新的 `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`s 間的賦值與復制構造是符合直覺的,因為它的語義與單個元素是相同的。通過下面這個例子,我們來看看如何在 `tuple`s 間的派生類至基類的賦值中獲得多態行為。
```
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`s 中,指針不總是指向需要進行內存管理的東西的。(參考 "[Library 1](../Text/content.html#ch01): [Smart_ptr 1](../Text/content.html#ch01)",可獲得 Boost 強大的智能指針的更多信息)
### 比較 Tuples
要比較 `tuple`s, 你必須包含頭文件 `"boost/tuple/tuple_comparison.hpp"`. `tuple` 的關系操作符有 `==`,`!=`,`<`,`>`,`<=` 和 `>=`, 它們按順序地對要比較的 `tuple`s 中的每一對元素調用相應的操作符。這些比較是短路的,即只比較到可以得到正確結果為止。只有具有相同數量元素的 `tuple`s 可以進行比較,并且顯然兩個 `tuple`s 的對應的元素必須是可比較的。如果兩個 `tuple`s 的所有元素對都相等,則相等比較返回 `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`s, `tup1` 和 `tup2`, 并不是嚴格相同的類型,但它們的類型是可比較的。在第一組比較中,`tuple`s 的第一個元素值不同,而在第二組中,`tuple`s 是相同的。以下是程序的運行輸出。
```
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`s 可以被排序,這意味著它們可以在關聯容器中被排序。有些時候,我們需要按 `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>` 函數對象,來執行基于 `tuple`s 的第二個元素的排序。這類函數對象還有更多的應用,如用于查找指定的 `tuple` 元素。
### 綁定 Tuple 元素到變量
Boost.Tuple 庫的一個方便的特性是"綁定" `tuple`s 到變量。綁定者就是用重載函數模板 `boost::tie` 所創建的 `tuple`s,它的所有元素都是非`const` 引用類型。因此,`tie`s 必須使用左值進行初始化,從而 `tie` 的參數也必須是非 `const` 引用類型。由于結果 `tuple`s 具有非 `const` 引用類型,對這樣一個 `tuple` 的元素進行賦值,就會通過非 `const` 引用賦值給調用 `tie` 時的左值。這樣就綁定了一個已有變量給 `tuple`, tie 的名字由此而來!
以下例子首先示范了一個通過返回 `tuple` 獲得值的明顯的方法。然后,它示范了通過一個 `tie`d `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() {
//"老"方法
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::tuple`s 綁定值一樣。
```
std::pair<short,double> p(3,0.141592);
short s;
double d;
boost::tie(s,d)=p;
```
綁定 `tuple`s 不僅僅是方便使用;它有助于使代碼更為清晰。
### Tuples的流操作
在本章的每一個例子中,取出 `tuple`s 的元素都只是為了能夠把它們輸出到 `std::cout`. 可以象前面那樣做,但還有更容易的方法。`tuple` 庫支持輸入和輸出流操作;`tuple` 重載了`operator>>` 和 `operator<<`。還有一些操縱器用于改變輸入輸出的缺省分隔符。對輸入操作改變分隔符改變 `operator>>` 查找元素值的結果。我們用一個簡單的讀寫 `tuple`s 的程序來測試一下這些情況。注意,要使用 `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`s 使用流操作符。`tuple`s 的缺省分隔符是:`(` (左括號) 作為開始分隔符,`)` (右括號) 作為結束分隔符,空格用于分隔各個 `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 的更多
還有很多我們沒有看到的工具可用于 `tuple`s。這些更為先進的特性對于創建使用 `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` 進行特化或函數重載。
剩下的問題就是取出第一個元素的值,然后繼續,并在列表尾部結束。`tuple`s 提供了成員函數 `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);
}
```
如果你不太清楚模板元編程的運用,不用擔心,對于使用 Tuple 庫這不是必需的。雖然這類代碼有時會用到,它的思想其實也很簡單。`largest_type` 從兩個輔助類模板 `largest_type_helper` 中的一個獲得 `typedef` ,具體使用哪一個就要靠那個布爾參數來特化了。這個參數通過比較 `tuple` (第二個模板參數) 的頭兩個元素的大小來決定。這樣的結果是,`typedef` 會表現為兩個類型中較大的那一個。我們的函數 `sum` 使用這個類型來作為返回值的類型,剩下的部分就是簡單地對兩個元素進行相加了。
這個例子的剩余部分示范了如何使用函數 `sum`, 還有如何從某個 `tuple` 元素的類型來聲明變量。頭兩個對 `tuple` 中的索引使用了硬編碼。
```
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;
```
### Tuples 與 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` 發現沒有元素了,就返回一個 `null_type` 實例,它匹配了那個非遞歸的 `for_each_element` 重載版本,從而結束了遞歸。這就是 `for_each_element` 的全部!
接下來,這個例子給出了兩個示例函數對象,它們所用的技術可以在其它情況下重用。一個是 `print` 函數對象。
```
struct print {
template <typename T> void operator()(const T& t) {
std::cout << t << '\n';
}
};
```
這個 `print` 函數對象沒什么奇怪的地方,但正如它所做的那樣,許多程序員不知道調用操作符可以是模板的!通常,函數對象可以對一個或多個類型進行特化,但對于 `tuples` 這樣不行,因為它的元素可以是完全不同的類型。因此,不是對于函數對象本身進行特化,而是對調用操作符進行特化,這樣做的另一個好處是它更簡單,如下。
```
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 庫使用的函數對象可能沒有這么多特性,它們也可以用于這種技術或其它的用法。
- 序
- 前言
- Acknowledgments
- 關于作者
- 本書的組織結構
- Boost的介紹
- 字符串及文本處理
- 數 據結構, 容器, 迭代器, 和算法
- 函數對象及高級編程
- 泛 型編程與模板元編程
- 數學及數字處理
- 輸入/輸出
- 雜項
- Part I: 通用庫
- Library 1. Smart_ptr
- Smart_ptr庫如何改進你的程序?
- 何時我們需要智能指針?
- Smart_ptr如何適應標準庫?
- scoped_ptr
- scoped_array
- shared_ptr
- shared_array
- intrusive_ptr
- weak_ptr
- Smart_ptr總結
- Library 2. Conversion
- Conversion 庫如何改進你的程序?
- polymorphic_cast
- polymorphic_downcast
- numeric_cast
- lexical_cast
- Conversion 總結
- Library 3. Utility
- Utility 庫如何改進你的程序?
- BOOST_STATIC_ASSERT
- checked_delete
- noncopyable
- addressof
- enable_if
- Utility 總結
- Library 4. Operators
- Operators庫如何改進你的程序?
- Operators
- 用法
- Operators 總結
- Library 5. Regex
- Regex庫如何改進你的程序?
- Regex 如何適用于標準庫?
- Regex
- 用法
- Regex 總結
- Part II: 容器及數據結構
- Library 6. Any
- Any 庫如何改進你的程序?
- Any 如何適用于標準庫?
- Any
- 用法
- Any 總結
- Library 7. Variant
- Variant 庫如何改進你的程序?
- Variant 如何適用于標準庫?
- Variant
- 用法
- Variant 總結
- Library 8. Tuple
- Tuple 庫如何改進你的程序?
- Tuple 庫如何適用于標準庫?
- Tuple
- 用法
- Tuple 總結
- Part III: 函數對象與高級編程
- Library 9. Bind
- Bind 庫如何改進你的程序?
- Bind 如何適用于標準庫?
- Bind
- 用法
- Bind 總結
- Library 10. Lambda
- Lambda 庫如何改進你的程序?
- Lambda 如何適用于標準庫?
- Lambda
- 用法
- Lambda 總結
- Library 11. Function
- Function 庫如何改進你的程序?
- Function 如何適用于標準庫?
- Function
- 用 法
- Function 總結
- Library 12. Signals
- Signals 庫如何改進你的程序?
- Signals 如何適用于標準庫?
- Signals
- 用法
- Signals 總結