C++/Tuple
外观
< C++
<tuple> 是从C++11正式引入C++标准程式库中的一个头文件,定义了C++标准支持的包含固定个数的元素的容器,这些元素可以是不同数据类型。
类(模板)
[编辑]- tuple:类模板,实现了一种容器,包含的元素个数固定,元素可以是不同类项。是std::pair的推广。
- tuple的初始化构造函数都是explicit,所以
tuple<int,int> t(1,2);
通过编译而tuple<int,int> t2 ={1,2};
报错
- tuple的初始化构造函数都是explicit,所以
- tuple_size<tuple_type>::value:类模板特化,编译时获得tuple、pair、array的元素个数
- tuple_element<idx,tupletype>::type:类模板特化,获取特定元素的类型
- std::uses_allocator<std::tuple>:类模板特化,指出std::uses_allocator的type trait
常量
[编辑]- ignore:使用tie解开一个tuple时,跳过一个元素的占位符
函数(模板)
[编辑]- make_tuple:函数模板,创建一个tuple对象
- tie:函数模板,创建一个完全由左值引用作为实参而构造出的tuple,其效果相当于解开(unpack)一个tuple的当前值赋给一组单个的对象;其中不关心的元素用std::ignore对应。例如:
std::tuple<int,std::ignore,string> t(1,2,"hello");
- forward_as_tuple:函数模板,创建一个引用到实参的tuple。如果实参为右值,tuple的数据成员是右值引用;否则为左值引用。
- tuple_cat:函数模板,通过连接任意数量的tuple来创建一个tuple
std::get<size_t index>(std::tuple)
或者std::get<typename>(std::tuple)
:函数模板,访问tuple的特定元素- operator== :函数模板,
- operator!= :函数模板,
- operator< :函数模板,
- operator<= :函数模板,
- operator> :函数模板,
- operator>= :函数模板,
- std::swap(std::tuple):函数模板,std::swap算法的特化
- apply:函数模板,C++17引入。调用一个函数,使用tuple作为实参
- make_from_tuple:函数模板,C++17引入。构造一个对象,使用tuple作为实参
例子
[编辑]#include <iostream>
#include <tuple>
#include <functional>
int main()
{
auto t1 = std::make_tuple(10, "Test", 3.14);
std::cout << "The value of t1 is "
<< "(" << std::get<0>(t1) << ", " << std::get<1>(t1)
<< ", " << std::get<2>(t1) << ")\n";
int n = 1;
auto t2 = std::make_tuple(std::ref(n), n);
n = 7;
std::cout << "The value of t2 is "
<< "(" << std::get<0>(t2) << ", " << std::get<1>(t2) << ")\n";
}
运行结果为:
The value of t1 is (10, Test, 3.14) The value of t2 is (7, 1)
下属例子对于函数或成员函数f,传入一个元组作为参数并转化为不定长参数调用:
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
template<size_t N>
struct Apply { // convert the n-element tuple to variable-length parameter
template<typename F, typename T, typename... A>
static inline auto apply(F && f, T && t, A &&... a)
-> decltype(Apply<N - 1>::apply(
::std::forward<F>(f), ::std::forward<T>(t),
::std::get<N - 1>(::std::forward<T>(t)), ::std::forward<A>(a)...
))
{
return Apply<N - 1>::apply(::std::forward<F>(f), ::std::forward<T>(t),
::std::get<N - 1>(::std::forward<T>(t)), ::std::forward<A>(a)...
);
}
};
template<>
struct Apply<0> {
template<typename F, typename T, typename... A>
static inline auto apply(F && f, T &&, A &&... a)
-> decltype(::std::forward<F>(f)(::std::forward<A>(a)...))
{
return ::std::forward<F>(f)(::std::forward<A>(a)...);
}
};
template<typename F, typename T>
inline auto apply(F && f, T && t) // call f by tuple t as parameter
-> decltype(Apply< ::std::tuple_size< typename ::std::decay<T>::type>::value
>::apply(::std::forward<F>(f), ::std::forward<T>(t)))
{
return Apply< ::std::tuple_size<typename ::std::decay<T>::type>::value
>::apply
(::std::forward<F>(f), ::std::forward<T>(t));
}
using std::forward; // You can change this if you like unreadable code or care hugely about namespace pollution.
template<size_t N>
struct ApplyMember
{
template<typename C, typename F, typename T, typename... A>
static inline auto apply(C&& c, F&& f, T&& t, A&&... a) ->
decltype(ApplyMember<N - 1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N - 1>(forward<T>(t)), forward<A>(a)...))
{
return ApplyMember<N - 1>::apply(forward<C>(c), forward<F>(f), forward<T>(t), std::get<N - 1>(forward<T>(t)), forward<A>(a)...);
}
};
template<>
struct ApplyMember<0>
{
template<typename C, typename F, typename T, typename... A>
static inline auto apply(C&& c, F&& f, T&&, A&&... a) ->
decltype((forward<C>(c)->*forward<F>(f))(forward<A>(a)...))
{
return (forward<C>(c)->*forward<F>(f))(forward<A>(a)...);
}
};
// C is the class, F is the member function, T is the tuple.
template<typename C, typename F, typename T>
inline auto apply(C&& c, F&& f, T&& t) ->
decltype(ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t)))
{
return ApplyMember<std::tuple_size<typename std::decay<T>::type>::value>::apply(forward<C>(c), forward<F>(f), forward<T>(t));
}
一个完整、详尽例子:
#include "tuple.hpp"
#include <iostream>
#include <tuple>
#include <string>
#include <functional>
#include <utility>
//////////////////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/tuple/tuple/
int test_tuple_4()
{
{ // tuple::tuple: Constructs a tuple object. This involves individually constructing its elements,
// with an initialization that depends on the constructor form invoke
std::tuple<int, char> first; // default
std::tuple<int, char> second(first); // copy
std::tuple<int, char> third(std::make_tuple(20, 'b')); // move
std::tuple<long, char> fourth(third); // implicit conversion
std::tuple<int, char> fifth(10, 'a'); // initialization
std::tuple<int, char> sixth(std::make_pair(30, 'c')); // from pair / move
std::cout << "sixth contains: " << std::get<0>(sixth);
std::cout << " and " << std::get<1>(sixth) << '\n';
}
{ // std::tuple::operator=: Each of the elements in the tuple object is assigned its corresponding element
std::pair<int, char> mypair(0, ' ');
std::tuple<int, char> a(10, 'x');
std::tuple<long, char> b, c;
b = a; // copy assignment
c = std::make_tuple(100L, 'Y'); // move assignment
a = c; // conversion assignment
c = std::make_tuple(100, 'z'); // conversion / move assignment
a = mypair; // from pair assignment
a = std::make_pair(2, 'b'); // form pair /move assignment
std::cout << "c contains: " << std::get<0>(c);
std::cout << " and " << std::get<1>(c) << '\n';
}
{ // std::tuple::swap: Exchanges the content of the tuple object by the content of tpl,
// which is another tuple of the same type (containing objects of the same types in the same order)
std::tuple<int, char> a(10, 'x');
std::tuple<int, char> b(20, 'y');
a.swap(b);
std::cout << "a contains: " << std::get<0>(a);
std::cout << " and " << std::get<1>(a) << '\n';
std::swap(a, b);
std::cout << "a contains: " << std::get<0>(a);
std::cout << " and " << std::get<1>(a) << '\n';
}
{ // std::relational operators: Performs the appropriate comparison operation between the tuple objects lhs and rhs
std::tuple<int, char> a(10, 'x');
std::tuple<char, char> b(10, 'x');
std::tuple<char, char> c(10, 'y');
if (a == b) std::cout << "a and b are equal\n";
if (b != c) std::cout << "b and c are not equal\n";
if (b<c) std::cout << "b is less than c\n";
if (c>a) std::cout << "c is greater than a\n";
if (a <= c) std::cout << "a is less than or equal to c\n";
if (c >= b) std::cout << "c is greater than or equal to b\n";
}
return 0;
}
////////////////////////////////////////////////
// reference: https://msdn.microsoft.com/en-us/library/bb982771.aspx
int test_tuple_3()
{
typedef std::tuple<int, double, int, double> Mytuple;
Mytuple c0(0, 1, 2, 3);
// display contents " 0 1 2 3"
std::cout << " " << std::get<0>(c0);
std::cout << " " << std::get<1>(c0);
std::cout << " " << std::get<2>(c0);
std::cout << " " << std::get<3>(c0);
std::cout << std::endl;
Mytuple c1;
c1 = c0;
// display contents " 0 1 2 3"
std::cout << " " << std::get<0>(c1);
std::cout << " " << std::get<1>(c1);
std::cout << " " << std::get<2>(c1);
std::cout << " " << std::get<3>(c1);
std::cout << std::endl;
std::tuple<char, int> c2(std::make_pair('x', 4));
// display contents " x 4"
std::cout << " " << std::get<0>(c2);
std::cout << " " << std::get<1>(c2);
std::cout << std::endl;
Mytuple c3(c0);
// display contents " 0 1 2 3"
std::cout << " " << std::get<0>(c3);
std::cout << " " << std::get<1>(c3);
std::cout << " " << std::get<2>(c3);
std::cout << " " << std::get<3>(c3);
std::cout << std::endl;
typedef std::tuple<int, float, int, float> Mytuple2;
Mytuple c4(Mytuple2(4, 5, 6, 7));
// display contents " 4 5 6 7"
std::cout << " " << std::get<0>(c4);
std::cout << " " << std::get<1>(c4);
std::cout << " " << std::get<2>(c4);
std::cout << " " << std::get<3>(c4);
std::cout << std::endl;
return (0);
}
///////////////////////////////////////////////////
// reference: http://zh.cppreference.com/w/cpp/utility/tuple
static std::tuple<double, char, std::string> get_student(int id)
{
if (id == 0) return std::make_tuple(3.8, 'A', "Lisa Simpson");
if (id == 1) return std::make_tuple(2.9, 'C', "Milhouse Van Houten");
if (id == 2) return std::make_tuple(1.7, 'D', "Ralph Wiggum");
throw std::invalid_argument("id");
}
int test_tuple_2()
{
auto student0 = get_student(0);
std::cout << "ID: 0, "
<< "GPA: " << std::get<0>(student0) << ", "
<< "grade: " << std::get<1>(student0) << ", "
<< "name: " << std::get<2>(student0) << '\n';
double gpa1;
char grade1;
std::string name1;
std::tie(gpa1, grade1, name1) = get_student(1);
std::cout << "ID: 1, "
<< "GPA: " << gpa1 << ", "
<< "grade: " << grade1 << ", "
<< "name: " << name1 << '\n';
return 0;
}
///////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/tuple/
static void print_pack(std::tuple<std::string&&, int&&> pack)
{
std::cout << std::get<0>(pack) << ", " << std::get<1>(pack) << '\n';
}
static void fun(int &a)
{
a = 15;
}
int test_tuple_1()
{
{ // std::tuple_element: class template, Class designed to access the type of the Ith element in a tuple.
// It is a simple class with a single member type, tuple_element::type,
// defined as an alias of the type of the Ith element in a tuple of type T.
auto mytuple = std::make_tuple(10, 'a');
std::tuple_element<0, decltype(mytuple)>::type first = std::get<0>(mytuple);
std::tuple_element<1, decltype(mytuple)>::type second = std::get<1>(mytuple);
std::cout << "mytuple contains: " << first << " and " << second << '\n';
}
{ // std::tuple_size: Class template designed to access the number of elements in a tuple
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::forward_as_tuple: function template, Constructs a tuple object with rvalue references
// to the elements in args suitable to be forwarded as argument to a function.
std::string str("John");
print_pack(std::forward_as_tuple(str + " Smith", 25));
print_pack(std::forward_as_tuple(str + " Daniels", 22));
}
{ // std::get: funtion template, Returns a reference to the Ith element of tuple tpl.
std::tuple<int, char> mytuple(10, 'a');
std::get<0>(mytuple) = 20;
std::cout << "mytuple contains: ";
std::cout << std::get<0>(mytuple) << " and " << std::get<1>(mytuple);
std::cout << std::endl;
}
{ // std::make_tuple: function template, Constructs an object of the appropriate tuple type
// to contain the elements specified in args
auto first = std::make_tuple(10, 'a'); // tuple < int, char >
const int a = 0; int b[3]; // decayed types:
auto second = std::make_tuple(a, b); // tuple < int, int* >
auto third = std::make_tuple(std::ref(a), "abc"); // tuple < const int&, const char* >
std::cout << "third contains: " << std::get<0>(third);
std::cout << " and " << std::get<1>(third);
std::cout << std::endl;
}
{ // std::tie: function template, Constructs a tuple object whose elements are references
// to the arguments in args, in the same order
// std::ignore: object, This object ignores any value assigned to it. It is designed to be used as an
// argument for tie to indicate that a specific element in a tuple should be ignored.
int myint;
char mychar;
std::tuple<int, float, char> mytuple;
mytuple = std::make_tuple(10, 2.6, 'a'); // packing values into tuple
std::tie(myint, std::ignore, mychar) = mytuple; // unpacking tuple into variables
std::cout << "myint contains: " << myint << '\n';
std::cout << "mychar contains: " << mychar << '\n';
}
{ // std::tuple_cat: function template, Constructs an object of the appropriate tuple type
// to contain a concatenation of the elements of all the tuples in tpls, in the same order
std::tuple<float, std::string> mytuple(3.14, "pi");
std::pair<int, char> mypair(10, 'a');
auto myauto = std::tuple_cat(mytuple, std::tuple<int, char>(mypair));
std::cout << "myauto contains: " << '\n';
std::cout << std::get<0>(myauto) << '\n';
std::cout << std::get<1>(myauto) << '\n';
std::cout << std::get<2>(myauto) << '\n';
std::cout << std::get<3>(myauto) << '\n';
}
{ // tuple::tuple: A tuple is an object capable to hold a collection of elements.
// Each element can be of a different type.
std::tuple<int, char> foo(10, 'x');
auto bar = std::make_tuple("test", 3.1, 14, 'y');
std::get<2>(bar) = 100; // access element
int myint; char mychar;
std::tie(myint, mychar) = foo; // unpack elements
std::tie(std::ignore, std::ignore, myint, mychar) = bar; // unpack (with ignore)
mychar = std::get<3>(bar);
std::get<0>(foo) = std::get<2>(bar);
std::get<1>(foo) = mychar;
std::cout << "foo contains: ";
std::cout << std::get<0>(foo) << ' ';
std::cout << std::get<1>(foo) << '\n';
}
{
std::tuple<int, char> foo{ 12, 'a' };
std::cout << std::get<0>(foo) << "\n"; // 12
fun(std::get<0>(foo));
std::cout << std::get<0>(foo) << "\n"; // 15
}
return 0;
}
高级例子:编译时构造访问tuple各个元素的跳表
[编辑]由于std::get函数模板必须在编译时指定模板参数N,程序在运行时要动态访问tuple第k个元素,可以提前在编译时构造一个跳表然后访问。通过预生成跳转表,将运行时索引映射到编译期常量。零开销抽象:生成的代码等效于手写的 switch-case,但无需手动维护。泛化能力:可应用于任何需要编译期索引的场景(如 std::tuple、std::variant)。
#include <array>
#include <type_traits>
//将索引 I 包装为一个编译期常量类型。通过类型携带索引值,使函数模板能基于索引值进行编译期分发。
template <std::size_t I>
using index_constant = std::integral_constant<std::size_t, I>;
namespace _ {
//调用访问器 vis,并传递编译期常量 index_constant<I>。将运行时函数调用与编译期索引 I 关联。
template <std::size_t I, typename VisRef>
static void visitor_invoker(VisRef vis) { vis(index_constant<I>{}); }
//生成一个函数指针数组 table,每个元素对应一个特定索引的 visitor_invoker 实例。通过 std::index_sequence<Is...> 展开索引序列,为每个 Is 生成对应的函数指针。
template <std::size_t, typename, typename>
struct jump_table_impl;
template <std::size_t Max, std::size_t... Is, typename VisRef>
struct jump_table_impl<Max, std::index_sequence<Is...>, VisRef> {
using func = void(VisRef);
static constexpr std::array<func*, Max> table{visitor_invoker<Is, VisRef>...};
};
//生成从 0 到 Max-1 的跳转表。当 Max=2 时,生成 {visitor_invoker<0>, visitor_invoker<1>}。
template <std::size_t Max, typename VisRef>
struct jump_table : jump_table_impl<Max, std::make_index_sequence<Max>, VisRef> {};
} // namespace _
/**
* @brief "Constantify" a dynamic index in given range.
*
* This is similar to a switch-case.
*/
// 根据运行时索引 idx 调用对应的编译期函数。
// 流程:
// 通过 jump_table 获取预生成的函数指针数组。
// 用 idx 索引数组,调用对应的 visitor_invoker<idx>。
visitor_invoker 将 index_constant<idx> 传递给访问器。
template <std::size_t max, typename Visitor>
void visit_index(std::size_t idx, Visitor&& vis) {
_::jump_table<max, Visitor&&>::table[idx](std::forward<Visitor>(vis));
}
// --------------------------------------------------------------------------
#include <tuple>
#include <iostream>
int main() {
auto t = std::tuple<char, int>('v', 42);
for (std::size_t i = 0; i < 2; ++i) {
visit_index<2>(i, [&] <std::size_t I> (index_constant<I>) {
std::cout << std::get<I>(t) << '\n';
});
}
}