tuple是一个固定大小的不同类型值的集合,是泛化的std::pair。和c#中的tuple类似,但是比c#中的tuple强大得多。我们也可以把他当做一个通用的结构体来用,不需要创建结构体又获取结构体的特征,在某些情况下可以取代结构体使程序更简洁,直观。
基本用法
构造一个tuple
tuple<const char*, int>tp = make_tuple(sendPack,nSendSize); //构造一个tuple
这个tuple等价于一个结构体
struct A { char* p; int len; };
用tuple<const char*, int>tp就可以不用创建这个结构体了,而作用是一样的,是不是更简洁直观了。还有一种方法也可以创建元组,用std::tie,它会创建一个元组的左值引用。
auto tp = return std::tie(1, "aa", 2); //tp的类型实际是: std::tuple<int&,string&, int&>
再看看如何获取它的值:
const char* data = tp.get<0>(); //获取第一个值 int len = tp.get<1>(); //获取第二个值
还有一种方法也可以获取元组的值,通过std::tie解包tuple
int x,y; string a; std::tie(x,a,y) = tp;
通过tie解包后,tp中三个值会自动赋值给三个变量。
解包时,我们如果只想解某个位置的值时,可以用std::ignore占位符来表示不解某个位置的值。比如我们只想解第三个值时:
std::tie(std::ignore,std::ignore,y) = tp; //只解第三个值了
还有一个创建右值的引用元组方法:forward_as_tuple。
std::map<int, std::string> m; m.emplace(std::forward_as_tuple(10, std::string(20, 'a')));
它实际上创建了一个类似于std::tuple<int&&, std::string&&>类型的tuple。
我们还可以通过tuple_cat连接多个tupe
int main() { std::tuple<int, std::string, float> t1(10, "Test", 3.14); int n = 7; auto t2 = std::tuple_cat(t1, std::make_pair("Foo", "bar"), t1, std::tie(n)); n = 10; print(t2); }
输出结果:
(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)
到这里tuple的用法介绍完了,是不是很简单,也很容易使用,相信你使用它之后就离不开它了。我前面说过tuple是简约而不简单。它有很多高级的用法。它和模板元关系密切,要介绍它的高级用法的时候,读者需要一定的模板元基础,如果你只是把它当一个泛型的pair去使用时,这部分可以不看,如果你想用它高级用法的时候就往下看。让我们要慢慢揭开tuple神秘的面纱。
tuple的高级用法
获取tuple中某个位置元素的类型
通过std::tuple_element获取元素类型。
template<typename Tuple> void Fun(Tuple& tp) { std::tuple_element<0,Tuple>::type first = std::get<0> (mytuple); std::tuple_element<1,Tuple>::type second = std::get<1> (mytuple); }
获取tuple中元素的个数:
tuple t;
int size = std::tuple_size<decltype(t))>::value;
遍历tuple中的每个元素
因为tuple的参数是变长的,也没有for_each函数,如果我们想遍历tuple中的每个元素,需要自己写代码实现。比如我要打印tuple中的每个元素。
template<class Tuple, std::size_t N> struct TuplePrinter { static void print(const Tuple& t) { TuplePrinter<Tuple, N - 1>::print(t); std::cout << ", " << std::get<N - 1>(t); } }; template<class Tuple> struct TuplePrinter<Tuple, 1>{ static void print(const Tuple& t) { std::cout << std::get<0>(t); } }; template<class... Args> void PrintTuple(const std::tuple<Args...>& t) { std::cout << "("; TuplePrinter<decltype(t), sizeof...(Args)>::print(t); std::cout << ")\n"; }
根据tuple元素值获取其对应的索引位置
namespace detail { template<int I, typename T, typename... Args> struct find_index { static int call(std::tuple<Args...> const& t, T&& val) { return (std::get<I - 1>(t) == val) ? I - 1 : find_index<I - 1, T, Args...>::call(t, std::forward<T>(val)); } }; template<typename T, typename... Args> struct find_index<0, T, Args...> { static int call(std::tuple<Args...> const& t, T&& val) { return (std::get<0>(t) == val) ? 0 : -1; } }; } template<typename T, typename... Args> int find_index(std::tuple<Args...> const& t, T&& val) { return detail::find_index<0, sizeof...(Args) - 1, T, Args...>:: call(t, std::forward<T>(val)); } int main() { std::tuple<int, int, int, int> a(2, 3, 1, 4); std::cout << find_index(a, 1) << std::endl; // Prints 2 std::cout << find_index(a, 2) << std::endl; // Prints 0 std::cout << find_index(a, 5) << std::endl; // Prints -1 (not found) }
展开tuple,并将tuple元素作为函数的参数。这样就可以根据需要对tuple元素进行处理了
#include <tuple> #include <type_traits> #include <utility> template<size_t N> struct Apply { 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) -> 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)); } void one(int i, double d) { std::cout << "function one(" << i << ", " << d << ");\n"; } int two(int i) { std::cout << "function two(" << i << ");\n"; return i; } //测试代码 int main() { std::tuple<int, double> tup(23, 4.5); apply(one, tup); int d = apply(two, std::make_tuple(2)); return 0; }
看到这里,想必大家对tuple有了一个全面的认识了吧,怎么样,它是简约而不简单吧。对模板元不熟悉的童鞋可以不看tuple高级用法部分,不要为看不懂而捉急,没事的,高级部分一般用不到,知道基本用法就够用了。
tuple和vector比较:
vector只能容纳同一种类型的数据,tuple可以容纳任意类型的数据;
vector和variant比较:
二者都可以容纳不同类型的数据,但是variant的类型个数是固定的,而tuple的类型个数不是固定的,是变长的,更为强大。
The decltype operator yields the type of a specified expression_r. The decltype operator, together with the auto keyword, is useful primarily to developers who write template libraries. Use auto and decltype to declare a template function whose return type depends on the types of its template arguments. Or, use auto and decltype to declare a template function that wraps a call to another function, and then returns the return type of the wrapped function.
#include <iostream>
#include <string>
#include <utility>
#include <iomanip>
int var;
const int&& fx();
struct A {
};
const A* a = new A();
decltype(fx()) aa = 20;
decltype(var) ab = 10;
decltype(a->x) ac = 1.0;
decltype((a->x)) ad = 2.0;
template<typename T, typename U>
auto myFunc(T&& t, U&& u) -> decltype (forward<T>(t) + forward<U>(u))
template<typename T1, typename T2>
auto Plus(T1&& t1, T2&& t2) ->
{
}
class X
{
public:
private:
};
int main(int argc,char** argv){
}
在C++中经常要用到很长的变量名,如果已经有变量和你将使用的变量是一个类型,即可使用decltype关键字
来申明一样的类型变量。
decltype原理
返回现有变量类型,decltype是一个关键字,而不是一个函数,这有啥区别呢?decltype在编译阶段返回变量类
型,而不是在运行阶段传递不同变量返回不同值。
decltype使用范例
1、复杂已知变量类型
- map<string, vector<string>> str_map;
- decltype(str_map) str_map_new;
map<string, vector<string>> str_map;
decltype(str_map) str_map_new;
2、表达式返回值类型
- int a, b;
- decltype(a + b) a;
int a, b;
decltype(a + b) a;
3、函数返回值
- int foo(int i) {
- return i;
- }
- double foo(double d) {
- return d;
- }
- template<typename T>
- auto getNum(T t)->decltype(foo(t)) {
- return foo(t);
- }
int foo(int i) {
return i;
}
double foo(double d) {
return d;
}
template<typename T>
auto getNum(T t)->decltype(foo(t)) {
return foo(t);
}
注意
1、decltype两个括号返回变量引用类型
- int i;
- decltype((i)) r = i;
- decltype(i) a;
int i;
decltype((i)) r = i;
decltype(i) a;
2、auto和decltype配合使用可以实现不同返回类型
返回值 decltype(表达式)
[返回值的类型是表达式参数的类型]
这个可也用来决定表达式的类型,就像Bjarne暗示的一样,如果我们需要去初始化某种类型的变量,auto是最简单的选择,但是如果我们所需的类型不是一个变量,例如返回值这时我们可也试一下decltype。
现在我们回看一些例子我们先前做过的,
- template <class U, class V>
- void Somefunction(U u, V v)
- {
- result = u*v;//now what type would be the result???
- decltype(u*v) result = u*v;//Hmm .... we got what we want
- }
template <class U, class V>
void Somefunction(U u, V v)
{
result = u*v;//now what type would be the result???
decltype(u*v) result = u*v;//Hmm .... we got what we want
}
在下面的一个段落我将会让你熟悉这个观念用 auto 和 decltype 来声明模板函数的返回值,其类型依靠模板参数。
1. 如果这个表达式是个函数,decltype 给出的类型为函数返回值的类型。
- int add(int i, int j){ return i+j; }
- decltype(add(5,6)) var = 5;//Here the type of var is return of add() -> which is int
int add(int i, int j){ return i+j; }
decltype(add(5,6)) var = 5;//Here the type of var is return of add() -> which is int
2.如果表达式是一个左值类型,那么 decltype 给出的类型为表达式左值引用类型。
- struct M { double x; };
- double pi = 3.14;
- const M* m = new M();
- decltype( (m->x) ) piRef = pi;
- // Note: Due to the inner bracets the inner statement is evaluated as expression,
- // rather than member 'x' and as type of x is double and as this is lvale
- // the return of declspec is double& and as 'm' is a const pointer
- // the return is actually const double&.
- // So the type of piRef is const double&
struct M { double x; };
double pi = 3.14;
const M* m = new M();
decltype( (m->x) ) piRef = pi;
// Note: Due to the inner bracets the inner statement is evaluated as expression,
// rather than member 'x' and as type of x is double and as this is lvale
// the return of declspec is double& and as 'm' is a const pointer
// the return is actually const double&.
// So the type of piRef is const double&
3.非常重要的标记一下,decltype 不会执行表达式而auto会,他仅仅推论一下表达式的类型。
- int foo(){}
- decltype( foo() ) x; // x is an int and note that
- // foo() is not actually called at runtime
int foo(){}
decltype( foo() ) x; // x is an int and note that
// foo() is not actually called at runtime
跟踪返回类型:
这对 C++ 开发者来说是一个全新的特性,直到现在函数的返回类型必须放在函数名的前面。到了 C++11,我们也可以将函数返回值的类型放在函数声明后,当然仅需要用 auto 替代返回类型。现在我们想知道怎么做,让我们来寻找答案:
- template<class U, class V>
- ??? Multiply(U u, V v) // how to specifiy the type of the return value
- {
- return u*v;
- }
template<class U, class V>
??? Multiply(U u, V v) // how to specifiy the type of the return value
{
return u*v;
}
我们明显的不能像这样:
- template<class U, class V>
- decltype(u*v) Multiply(U u, V v) // Because u & v are not defined before Multiply.
- // What to do...what to do !!!
- {
- return u*v;
- }
template<class U, class V>
decltype(u*v) Multiply(U u, V v) // Because u & v are not defined before Multiply.
// What to do...what to do !!!
{
return u*v;
}
这种情况我们可也使用 auto 然后当我们使用 decltype(u*v) 作为返回值这个类型便知晓了.
这是不是很酷?
- template<class U, class V>
- auto Multiply(U u, V v) -> decltype(u*v) // Note -> after the function bracet.
- {
- return u*v;
- }
最近群里比较热闹,大家都在山寨c++11的std::bind,三位童孩分别实现了自己的bind,代码分别在这里:
- 木头云的实现:连接稍后补上。
- mr.li的实现:https://code.google.com/p/y-code-svn/source/browse/#svn%2Ftrunk%2Fc%2B%2B%2FBex%2Fsrc%2FBex%2Fbind
- null的实现:http://www.cnblogs.com/xusd-null/p/3693817.html#2934538
这些实现思路和ms stl的std::bind的实现思路是差不多的,只是在实现的细节上有些不同。个人觉得木头云的实现更简洁,本文中的简单实现也是基于木头云的bind之上的,在此表示感谢。下面我们来分析一下bind的基本原理。
bind的基本原理
bind的思想实际上是一种延迟计算的思想,将可调用对象保存起来,然后在需要的时候再调用。而且这种绑定是非常灵活的,不论是普通函数、函数对象、还是成员函数都可以绑定,而且其参数可以支持占位符,比如你可以这样绑定一个二元函数auto f = bind(&func, _1, _2);,调用的时候通过f(1,2)实现调用。关于bind的用法更多的介绍可以参考我博客中介绍:http://www.cnblogs.com/qicosmos/p/3302144.html。
要实现一个bind需要解决两个问题,第一个是保存可调用对象及其形参,第二个是如何实现调用。下面来分析如何解决这两个问题。
保存可调用对象
实现bind的首先要解决的问题是如何将可调用对象保存起来,以便在后面调用。要保存可调用对象,需要保存两个东西,一个是可调用对象的实例,另一个是可调用对象的形参。保存可调用对象的实例相很简单,因为bind时直接要传这个可调用对象的,将其作为一个成员变量即可。而保存可调用对象的形参就麻烦一点,因为这个形参是变参,不能直接将变参作为成员变量。如果要保存变参的话,我们需要用tuple来将变参保存起来。
可调用对象的执行
bind的形参因为是变参,可以是0个,也可能是多个,大部分情况下是占位符,还有可能占位符和实参都有。正是由于bind绑定的灵活性,导致我们不得不在调用的时候需要找出哪些是占位符,哪些是实参。如果某个一参数是实参我们就不处理,如果是占位符,我们就要将这个占位符替换为对应的实参。比如我们绑定了一个三元函数:auto f = bind(&func, _1, 2, _2);调用时f(1,3);由于绑定时有三个参数,一个实参,两个占位符,调用时传入了两个实参,这时我们就要将占位符_1替换为实参1,占位符_2替换为实参3。这个占位符的替换需要按照调用实参的顺序来替换,如果调用时的实参个数比占位符要多,则忽略多余的实参。
调用的实参,我们也会先将其转换为tuple,用于在后面去替换占位符时,选取合适的实参。
bind实现的关键技术
将tuple展开为变参
前面讲到绑定可调用对象时,将可调用对象的形参(可能含占位符)保存起来,保存到tuple中了。到了调用阶段,我们就要反过来将tuple展开为可变参数,因为这个可变参数才是可调用对象的形参,否则就无法实现调用了。这里我们会借助于一个整形序列来将tuple变为可变参数,在展开tuple的过程中我们还需要根据占位符来选择合适实参,即占位符要替换为调用实参。
根据占位符来选择合适的实参
这个地方比较关键,因为tuple中可能含有占位符,我们展开tuple时,如果发现某个元素类型为占位符,则从调用的实参生成的tuple中取出一个实参,用来作为变参的一个参数;当某个类型不为占位符时,则直接从绑定时生成的形参tuple中取出参数,用来作为变参的一个参数。最终tuple被展开为一个变参列表,这时,这个列表中没有占位符了,全是实参,就可以实现调用了。这里还有一个细节要注意,替换占位符的时候,如何从tuple中选择合适的参数呢,因为替换的时候要根据顺序来选择。这里是通过占位符的模板参数I来选择,因为占位符place_holder<I>的实例_1实际上place_holder<1>, 占位符实例_2实际上是palce_holder<2>,我们是可以根据占位符的模板参数来获取其顺序的。
bind的简单实现
#include <tuple> #include <type_traits> using namespace std; template<int...> struct IndexTuple{}; template<int N, int... Indexes> struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...>{}; template<int... indexes> struct MakeIndexes<0, indexes...> { typedef IndexTuple<indexes...> type; }; template <int I> struct Placeholder { }; Placeholder<1> _1; Placeholder<2> _2; Placeholder<3> _3; Placeholder<4> _4; Placeholder<5> _5; Placeholder<6> _6; Placeholder<7> _7; Placeholder<8> _8; Placeholder<9> _9; Placeholder<10> _10; // result type traits template <typename F> struct result_traits : result_traits<decltype(&F::operator())> {}; template <typename T> struct result_traits<T*> : result_traits<T> {}; /* check function */ template <typename R, typename... P> struct result_traits<R(*)(P...)> { typedef R type; }; /* check member function */ template <typename R, typename C, typename... P> struct result_traits<R(C::*)(P...)> { typedef R type; }; template <typename T, class Tuple> inline auto select(T&& val, Tuple&)->T&& { return std::forward<T>(val); } template <int I, class Tuple> inline auto select(Placeholder<I>&, Tuple& tp) -> decltype(std::get<I - 1>(tp)) { return std::get<I - 1>(tp); } // The invoker for call a callable template <typename T> struct is_pointer_noref : std::is_pointer<typename std::remove_reference<T>::type> {}; template <typename T> struct is_memfunc_noref : std::is_member_function_pointer<typename std::remove_reference<T>::type> {}; template <typename R, typename F, typename... P> inline typename std::enable_if<is_pointer_noref<F>::value, R>::type invoke(F&& f, P&&... par) { return (*std::forward<F>(f))(std::forward<P>(par)...); } template <typename R, typename F, typename P1, typename... P> inline typename std::enable_if<is_memfunc_noref<F>::value && is_pointer_noref<P1>::value, R>::type invoke(F&& f, P1&& this_ptr, P&&... par) { return (std::forward<P1>(this_ptr)->*std::forward<F>(f))(std::forward<P>(par)...); } template <typename R, typename F, typename P1, typename... P> inline typename std::enable_if<is_memfunc_noref<F>::value && !is_pointer_noref<P1>::value, R>::type invoke(F&& f, P1&& this_obj, P&&... par) { return (std::forward<P1>(this_obj).*std::forward<F>(f))(std::forward<P>(par)...); } template <typename R, typename F, typename... P> inline typename std::enable_if<!is_pointer_noref<F>::value && !is_memfunc_noref<F>::value, R>::type invoke(F&& f, P&&... par) { return std::forward<F>(f)(std::forward<P>(par)...); } template<typename Fun, typename... Args> struct Bind_t { typedef typename decay<Fun>::type FunType; typedef std::tuple<typename decay<Args>::type...> ArgType; typedef typename result_traits<FunType>::type result_type; public: template<class F, class... BArgs> Bind_t(F& f, BArgs&... args) : m_func(f), m_args(args...) { } template<typename F, typename... BArgs> Bind_t(F&& f, BArgs&&... par) : m_func(std::move(f)), m_args(std::move(par)...) {} template <typename... CArgs> result_type operator()(CArgs&&... args) { return do_call(MakeIndexes<std::tuple_size<ArgType>::value>::type(), std::forward_as_tuple(std::forward<CArgs>(args)...)); } template<typename ArgTuple, int... Indexes > result_type do_call(IndexTuple< Indexes... >& in, ArgTuple& argtp) { return simple::invoke<result_type>(m_func, select(std::get<Indexes>(m_args), argtp)...); //return m_func(select(std::get<Indexes>(m_args), argtp)...); } private: FunType m_func; ArgType m_args; }; template <typename F, typename... P> inline Bind_t<F, P...> Bind(F&& f, P&&... par) { return Bind_t<F, P...>(std::forward<F>(f), std::forward<P>(par)...); } template <typename F, typename... P> inline Bind_t<F, P...> Bind(F& f, P&... par) { return Bind_t<F, P...>(f, par...); }View Code
测试代码:
void TestFun1(int a, int b, int c) { } void TestBind1() { Bind(&TestFun1, _1, _2, _3)(1, 2, 3); Bind(&TestFun1, 4, 5, _1)(6); Bind(&TestFun1, _1, 4, 5)(3); Bind(&TestFun1, 3, _1, 5)(4); }View Code
bind更多的实现细节
由于只是展示bind实现的关键技术,很多的实现细节并没有处理,比如参数是否是引用、右值、const volotile、绑定非静态的成员变量都还没处理,仅仅供学习之用,并非是重复发明轮子,只是展示bind是如何实现, 实际项目中还是使用c++11的std::bind为好。null同学还图文并茂的介绍了bind的过程:http://www.cnblogs.com/xusd-null/p/3698969.html,有兴趣的童孩可以看看.
关于bind的使用
在实际使用过程中,我更喜欢使用lambda表达式,因为lambda表达式使用起来更简单直观,lambda表达式在绝大多数情况下可以替代bind。
如果你觉得这篇文章对你有用,可以点一下推荐,谢谢。