C++11: std::tuple | De C++ et alias OOPscenitates (oopscenities.net)
A tuple
is a C++11 construction and it is built heavily on variadic templates.
A tuple
is a variadic class template that stores an unlimited set of values of different types, defined when instantiating the tuple; for example:
tuple<int, int> x;
will store 2 integers.
tuple<string, int, bool> y;
will store one string, one integer and one boolean and so on.
Tuples are useful for several things:
- They are light replacements for structures.
- They can be useful to return several values from a function
- They let you perform comparisons through all the value set
You can instantiate tuples in these ways:
tuple<int, int, bool> x; //instantiating but not initializing
tuple<int, string> y { 2, "hello" }; //instantiating AND initializing
auto z = make_tuple(2, 3, 4, "bye"s); //instantiating AND initializing throught the make_tuple helper function.
Consider you declare something like this:
auto xx = make_tuple(3.14, "PI"s);
To get the tuple values, you must to use the get<int>()
function like in this code excerpt:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include <tuple> #include <iostream> #include <string> using namespace std; int main() { auto xx = make_tuple(3.14, "PI" s); auto & first = get<0>(xx); auto & second = get<1>(xx); cout << "(" << first << "; " << second << ")" << endl; return 0; } |
The template parameter is the index of the element to be retrieved, so, if the first element was an integer, the index 0 will retrieve the integer stored as the first value in the tuple, the index 0 will retrieve the second one and so on.
Tuples are also very useful to create structs “on the fly” and to use them in data structures; for example:
1
2
3
4
5
6
7
8
9
10
11
12
|
int main() { vector<tuple< int , string>> vec; vec.push_back(make_tuple(10, "ten" )); vec.emplace_back(20, "twenty" ); vec.emplace_back(30, "thirty" ); for ( auto & i : vec) { cout << "(" << get<0>(i) << "; " << get<1>(i) << ")" << endl; } } |
In this case I did not need to create a new struct in order to store the number and the number name, for example.
Other nice thing on tuples is that you can compare tuples relying on the comparison operators of the data types inside the tuples.
Look at this code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#include <iostream> #include <set> #include <string> #include <tuple> using namespace std; using car = tuple<string, string, int >; void print( const car& c) { cout << get<0>(c) << ", " << get<1>(c) << "; " << get<2>(c) << endl; } int main() { set<car> cars; cars.emplace( "Toyota" , "Rav4" , 2012); cars.emplace( "VW" , "Jetta" , 2015); cars.emplace( "Chevrolet" , "Sonic" , 2013); cars.emplace( "BMW" , "X5" , 2014); cars.emplace( "VW" , "Jetta" , 2014); for ( auto & i : cars) print(i); cout << "******" << endl; auto it = cars.find(car { "Toyota" , "Rav4" , 2012 }); if (it == cars.end()) cerr << "CAR NOT FOUND" << endl; else print(*it); return 0; } |
When it is executed, this thing is displayed:
BMW, X5; 2014
Chevrolet, Sonic; 2013
Toyota, Rav4; 2012
VW, Jetta; 2014
VW, Jetta; 2015
******
Toyota, Rav4; 2012
The code and its execution contain several interesting things:
- I defined an alias to a tuple called “car”, having it, I was able to use the tuple as a declared type in a lot of cases… without declaring it!
- If I would have created a “car” struct, to use it inside a set, I would have to overload the operator<() for the struct, because the sets use this operator in order to insert the objects accordingly.
- When executing the program, all the objects where ordered by brand, by model and by year; using the tuple operator<(). If the first element in a tuple A is less than the first element in a tuple B, the operator returns TRUE. If the first element in tuple A is greater than the first element in tuple B, the operator returns FALSE; but if both elements are equal, the comparison is performed between the second elements and so on.
- Notice I created a car to pass it into the method find; I created it in exactly the same way I would have created a car struct instance.
- Notice the element is found in the set (because operator<() too).
Now I want to modify my set in order to store the elements ordered by year, by brand and by model. Look at the new implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
#include <iostream> #include <set> #include <string> #include <tuple> using namespace std; using car = tuple<string, string, int >; void print( const car& c) { cout << get<0>(c) << ", " << get<1>(c) << "; " << get<2>(c) << endl; } int main() { auto cc = []( auto & a, auto & b) { return tie(get<2>(a), get<0>(a), get<1>(a)) < tie(get<2>(b), get<0>(b), get<1>(b)); }; set<car, decltype (cc)> cars { cc }; cars.emplace( "Toyota" , "Rav4" , 2012); cars.emplace( "VW" , "Jetta" , 2015); cars.emplace( "Chevrolet" , "Sonic" , 2013); cars.emplace( "BMW" , "X5" , 2014); cars.emplace( "VW" , "Jetta" , 2014); for ( auto & i : cars) print(i); cout << "******" << endl; auto it = cars.find(car { "Toyota" , "Rav4" , 2012 }); if (it == cars.end()) cerr << "CAR NOT FOUND" << endl; else print(*it); return 0; } |
The output is:
Toyota, Rav4; 2012
Chevrolet, Sonic; 2013
BMW, X5; 2014
VW, Jetta; 2014
VW, Jetta; 2015
******
Toyota, Rav4; 2012
The elements are ordered first by year.
Notice my “cc” lambda expression. It uses a function called “tie”.
“tie” is a function that takes a set of references and creates a tuple containing references (not values or copies) to the original values. So, tie is useful to create temporary light tuples. In my example, using “tie”, I was able to create other set of tuples with different logical order, so the tuple operator<() algorithm worked using this new logical order.
1,元组简介
tuple是一个固定大小的不同类型值的集合,是泛化的std::pair。我们也可以把他当做一个通用的结构体来用,不需要创建结构体又获取结构体的特征,在某些情况下可以取代结构体使程序更简洁,直观。std::tuple理论上可以有无数个任意类型的成员变量,而std::pair只能是2个成员,因此在需要保存3个及以上的数据时就需要使用tuple元组了。
tuple(元组)在c++11中开始引用的。tuple看似简单,其实它是简约而不简单,可以说它是c++11中一个既简单又复杂的东东,关于它简单的一面是它很容易使用,复杂的一面是它内部隐藏了太多细节,要揭开它神秘的面纱时又比较困难。
2,tuple的创建和初始化
std::tuple<T1, T2, TN> t1; //创建一个空的tuple对象(使用默认构造),它对应的元素分别是T1和T2...Tn类型,采用值初始化。
std::tuple<T1, T2, TN> t2(v1, v2, ... TN); //创建一个tuple对象,它的两个元素分别是T1和T2 ...Tn类型; 要获取元素的值需要通过tuple的成员get<Ith>(obj)进行获取(Ith是指获取在tuple中的第几个元素,请看后面具体实例)。
std::tuple<T1&> t3(ref&); // tuple的元素类型可以是一个引用
std::make_tuple(v1, v2); // 像pair一样也可以通过make_tuple进行创建一个tuple对象
tuple的元素类型为引用:
std::string name;
std::tuple<string &, int> tpRef(name, 30);
// 对tpRef第一个元素赋值,同时name也被赋值 - 引用
std::get<0>(tpRef) = "Sven";
// name输出也是Sven
std::cout << "name: " << name << '\n';
3,有关tuple元素的操作
等价结构体
开篇讲过在某些时候tuple可以等同于结构体一样使用,这样既方便又快捷。如:
struct person {
char *m_name;
char *m_addr;
int *m_ages;
};
//可以用tuple来表示这样的一个结构类型,作用是一样的。
std::tuple<const char *, const char *, int>
2. 如何获取tuple元素个数
当有一个tuple对象但不知道有多少元素可以通过如下查询:
// tuple_size
#include <iostream> // std::cout
#include <tuple> // std::tuple, std::tuple_size
int main ()
{
std::tuple<int, char, double> mytuple (10, 'a', 3.14);
std::cout << "mytuple has ";
std::cout << std::tuple_size<decltype(mytuple)>::value;
std::cout << " elements." << '\n';
return 0;
}
//输出结果:
mytuple has 3 elements
3.如何获取元素的值
获取tuple对象元素的值可以通过get<Ith>(obj)方法进行获取;
Ith - 是想获取的元素在tuple对象中的位置。
obj - 是想获取tuple的对象
// tuple_size
#include <iostream> // std::cout
#include <tuple> // std::tuple, std::tuple_size
int main ()
{
std::tuple<int, char, double> mytuple (10, 'a', 3.14);
std::cout << "mytuple has ";
std::cout << std::tuple_size<decltype(mytuple)>::value;
std::cout << " elements." << '\n';
//获取元素
std::cout << "the elements is: ";
std::cout << std::get<0>(mytuple) << " ";
std::cout << std::get<1>(mytuple) << " ";
std::cout << std::get<2>(mytuple) << " ";
std::cout << '\n';
return 0;
}
//输出结果:
mytuple has 3 elements.
the elements is: 10 a 3.14
tuple不支持迭代,只能通过元素索引(或tie解包)进行获取元素的值。但是给定的索引必须是在编译器就已经给定,不能在运行期进行动态传递,否则将发生编译错误:
for(int i=0; i<3; i++)
std::cout << std::get<i>(mytuple) << " "; //将引发编译错误
4.获取元素的类型
要想得到元素类型可以通过tuple_element方法获取,如有以下元组对象:
std::tuple<std::string, int> tp("Sven", 20);
// 得到第二个元素类型
std::tuple_element<1, decltype(tp)>::type ages; // ages就为int类型
ages = std::get<1>(tp);
std::cout << "ages: " << ages << '\n';
//输出结果:
ages: 20
5.利用tie进行解包元素的值
如同pair一样也是可以通过tie进行解包tuple的各个元素的值。如下tuple对象有4个元素,通过tie解包将会把这4个元素的值分别赋值给tie提供的4个变量中。
#include <iostream>
#include <tuple>
#include <utility>
int main(int argc, char **argv) {
std::tuple<std::string, int, std::string, int> tp;
tp = std::make_tuple("Sven", 25, "Shanghai", 21);
// 定义接收变量
std::string name;
std::string addr;
int ages;
int areaCode;
std::tie(name, ages, addr, areaCode) = tp;
std::cout << "Output: " << '\n';
std::cout << "name: " << name <<", ";
std::cout << "addr: " << addr << ", ";
std::cout << "ages: " << ages << ", ";
std::cout << "areaCode: " << areaCode << '\n';
return 0;
}
//输出结果:
Output:
name: Sven, addr: Shanghai, ages: 25, areaCode: 21
但有时候tuple包含的多个元素时只需要其中的一个或两个元素,如此可以通过std::ignore进行变量占位,这样将会忽略提取对应的元素。可以修改上述例程:
#include <iostream>
#include <tuple>
#include <utility>
int main(int argc, char **argv) {
std::tuple<std::string, int, std::string, int> tp;
tp = std::make_tuple("Sven", 25, "Shanghai", 21);
// 定义接收变量
std::string name;
std::string addr;
int ages;
int areaCode = 110;
std::tie(name, ages, std::ignore, std::ignore) = tp;
std::cout << "Output: " << '\n';
std::cout << "name: " << name <<", ";
std::cout << "addr: " << addr << ", ";
std::cout << "ages: " << ages << ", ";
std::cout << "areaCode: " << areaCode << '\n';
return 0;
}
//输出结果:
Output:
name: Sven, addr: , ages: 25, areaCode: 110
6. tuple元素的引用
前面已经列举了将引用作为tuple的元素类型。下面通过引用搭配make_tuple()可以提取tuple的元素值,将某些变量值设给它们,并通过改变这些变量来改变tuple元素的值:
#include <iostream>
#include <tuple>
#include <functional>
int main(int argc, char **agrv) {
std::tuple<std::string, int, float> tp1("Sven Cheng", 77, 66.1);
std::string name;
int weight;
float f;
auto tp2 = std::make_tuple(std::ref(name), std::ref(weight), std::ref(f)) = tp1;
std::cout << "Before change: " << '\n';
std::cout << "name: " << name << ", ";
std::cout << "weight: " << weight << ", ";
std::cout << "f: " << f << '\n';
name = "Sven";
weight = 80;
f = 3.14;
std::cout << "After change: " << '\n';
std::cout << "element 1st: " << std::get<0>(tp2) << ", ";
std::cout << "element 2nd: " << std::get<1>(tp2) << ", ";
std::cout << "element 3rd: " << std::get<2>(tp2) << '\n';
return 0;
}
//输出结果:
Before change:
name: Sven Cheng, weight: 77, f: 66.1
After change:
element 1st: Sven, element 2nd: 80, element 3rd: 3.14
————————————————
版权声明:本文为CSDN博主「sevencheng798」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sevenjoin/article/details/88420885