您的位置:首页 > 编程语言 > C语言/C++

C++可变参数模板的小知识

2015-10-05 00:50 316 查看
函数模板的类型参数(即模板实参)可以推导,不需要显示的写出来;但是类模板的类型参数必须显示的写出来。

函数模板是不能偏特化的(可以全特化),这是很神奇的地方,貌似在C++11中仍然是这样。不过由于函数模板可以重载,一样可以达到偏特化的效果;实际上,函数模板偏特化和重载的界线非常模糊。

以下是全特化的例子:

template<typename T1, typename T2>
T1 f(T1 a, T2 b)
{
return a * b;
}

template<>
int f<int, int> (int a, int b)
{
return a * b;
}
但是,如果是偏特化,就报错了:

template<typename T>
T f<T, int> (T a, int b)
{
return a * b;
}
由于函数模板的类型参数在实例化时可以推导,假若视作把函数模板id的类型拿掉:

template<typename T>
T f (T a, int b)
{
return a * b;
}
这样就可以,此时,就变成了函数模板的重载。。。。。。 有意思吧,实际上,跟偏特化多么像哇(特化有两个<>括号,template后一个,名称后一个--和名称一起构成模板id)。所以说函数模板不能偏特化也没有什么,完全可以被重载取代。

自己利用可变参数模板实现的一个有std::function 部分功能的类:

template<typename Signature>
class myfunction
{

};

template<typename R, typename... Args>
class myfunction<R (Args...)>
{
typedef R (*Ptr)(Args...);
Ptr m_p;
public:
myfunction(Ptr p) : m_p(p)
{

}
R operator()(Args... args)
{
return m_p(args...);
}
};

template<typename R, typename T, typename ... Args>
class myfunction<R(T*, Args...)>
{
typedef R (T::* mPtr)(Args...);
mPtr m_p;
public:
myfunction(mPtr p):  m_p(p)
{

}
R operator()(T* likeThis, Args...args)
{
return   (likeThis->*m_p)(args...);
}
};

template<typename R, typename T, typename ... Args>
class myfunction<R(T, Args...)>
{
typedef R (T::* mPtr)(Args...);
mPtr m_p;
public:
myfunction(mPtr p):  m_p(p)
{

}
R operator()(T likeThis, Args...args)
{
return   (likeThis.*m_p)(args...);
}
};

template<typename R, typename T, typename ... Args>
class myfunction<R(T&, Args...)>
{
typedef R (T::* mPtr)(Args...);
mPtr m_p;
public:
myfunction(mPtr p):  m_p(p)
{

}
R operator()(T& likeThis, Args...args)
{
return   (likeThis.*m_p)(args...);
}
};


可以包装普通函数跟成员函数,标准库中的function,包装成员函数时,第一个参数可以是普通传值类型,也可以是指针类型,也可以是引用类型。

关于可变参数模板的参数包:打包的过程是将 ... 省略号的左右由逗号分离的一个模式打包一起;解包是按 ... 省略号前边的模式匹配着展开。

需要注意的是,后边的function<> 是前边的一个特化,虽然这里模板的类型参数的个数不相同。所以我的总结是,无论类模板还是函数模板,只要有两个尖括号<>, 一个在template 后,一个在模板id中,都是基本模板的一个特化版本,而不管模板的类型参数的个数。

函数模板的类型参数在实例化是可以推导,但是全特化(函数模板不支持偏特化),仍然需要写全函数模板的模板id,因为此时是定义。

下边是对 tuple 的解释:代码在这里

template <class... Ts> struct tuple {};   //这里是基本模板,实际上...模板参数包可以表示0个以上的类型参数
//所以这里是用来表示 0 个模板参数 用来结束递归
template <class T, class... Ts>             //如果另外有只需要一个模板参数的版本  会与T, ...Ts 版本冲突  因为Ts 可以表示没有
struct tuple<T, Ts...> : tuple<Ts...> {
tuple(T t, Ts... ts) : tuple<Ts...>(ts...), head(t) {}   //注意这里的包扩展   最后扩展成tuple<E1, E2, E3>(e1,e2,e3) 分两步展开 先展开前边的包 再后边的

T haed;           //展开之后  每一层里都有个独立的 head 成员
};

template <size_t, class> struct elem_type_holder;    //辅助模板  用来获取类型  注意这里的基本后边模板只是用到声明  所以无需定义

template <class T, class... Ts>
struct elem_type_holder<0, tuple<T, Ts...>> {    //名称相同的  带template后一个尖括号<>  名称后一个尖括号<>  都是基本模板的偏特化
typedef T type;
};

template <size_t k, class T, class... Ts>
struct elem_type_holder<k, <span style="color:#FF0000;">tuple<T, Ts...></span>> {   //注意这里每次剥离tuple  类型参数中的前边一个  通过k来计数控制 相当于在递归中使用计数来控制递归的层数
typedef typename elem_type_holder<k - 1, <span style="color:#FF0000;">tuple<Ts...></span>>::type type;   //从k-1 一直到 0 一共进行了k次  如此获得参数包第k个类型参数(最开始的T不在参数包中)
};

template <size_t k, class... Ts>
typename std::enable_if<    //enable_if在其第一个条件为真时  其成员类型才有效  否则会发生编译错误
k == 0, typename elem_type_holder<0, tuple<Ts...>>::type&>::type
get(tuple<Ts...>& t) {
return t.head;
}

template <size_t k, class T, class... Ts>
typename std::enable_if<
k != 0, typename elem_type_holder<k, tuple<T, Ts...>>::type&>::type    //注意这里返回值是引用
get(tuple<T, Ts...>& t) {     //这里也通过丢掉第一个类型参数  达到递归的效果
tuple<Ts...></span>& base = t;   //还有这里基类引用指向的派生类
return get<k - 1>(base);  //一直这么剥离下去  直到能匹配上边一个重载版本
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: