C++ 模板基础知识(二)


本次整理的模板基础知识是: 模板函数的返回值类型非类型模板参数模板实参为可调用对象,来自《C++模板》 第二版。

模板函数的返回值类型

通过下面三种方法可以使编译器确定模板函数的返回值类型

1. 引入第三个模板参数用于指定返回值的类型

在实例化时需要显示指定返回值类型

template<typename T1,typename T2,typename RT>
RT max(T1 a,T2 b);

显示指定返回值类型max<double>(4,7.2),如下例子:

#include <iostream>
//这里如果写成template<typename T1,typename T2,typename RT>会编译不过
//因为下面实例化时,double参数被指定为第一个形参的类型
template<typename RT,typename T1,typename T2>
RT max(T1 a,T2 b) {
    return b<a?a:b;
}

int main() {
   auto v = max<double>(9.9,10);
   std::cout<<v<<std::endl;
}
2. 让编译器自己推导出返回类型
template<typename T1,typename T2>
auto max(T1 a,T2 b) -> decltype (b<a?a:b) {
    return b<a?a:b;
}

上面是C++ 11的写法,decltype用于获取表达式的类型,通过decltype (b<a?a:b)推导出返回值的类型,推导是编译期完成。还可以写成这样:

template<typename T1,typename T2>
auto max(T1 a,T2 b) -> decltype (true?a:b);

区别是decltype内的表达式不同了,这里要注意b<a?a:btrue?a:b表达式是用于推导出类型而不是通过表达式结果得出类型,具体的可以看看这个链接: why-do-these-two-code-snippets-have-the-same-effect

下面是C++ 14及之后标准的写法,写法更简单些。

template<typename T1,typename T2>
auto max(T1 a,T2 b) {
    return b<a?a:b;
}
3. 使用std::common_type来获取返回值的类型,

它可以推导两种或多种不同类型的共同类型(即返回列表中类型参数都可以转换成的类型),将会返回一个数据结构包含一个类型成员,指示得到的类型,typename std::common_type<T1,T2>::type,使用方法如下:

#include <type_traits>
template<typename T1,typename T2>
typename std::common_type<T1,T2>::type max(T1 a,T2 b) {
    return b < a?a:b;
}

它的定义形式如下:

template <class... T>
struct comon_type;

如下例子:

#include <iostream>
#include <type_traits>
template<typename T1,typename T2>
typename std::common_type<T1,T2>::type max(T1 a,T2 b) {
    return b < a ?a:b;
}

int main() {
   //编译会报错,因为字符串与整型没有共同的类型
   //auto v = max("123",10);
   //通过整型9和浮点型10.1推导出返回类型为浮点型
   auto v = max(9,10.1);
   
   std::cout<<v<<std::endl;
}

可以通过cppinsights 查看模板实例化的结果,如下:

template<>
typename std::common_type<int, double>::type max<int, double>(int a, double b)
{
return b < static_cast(a) ? static_cast(a) : b;
}

可以看到推导出来的类型为double。

综合例子:
#include <iostream>
//1.引入第三个类型参数标识返回值
template<typename RT,typename T1,typename T2>
RT max1(T1 a,T2 b) {
    return b<a?a:b;
}

//2.让编译器自己推导
//编译器自动推导
template<typename T1,typename T2>
auto max2(T1 a,T2 b) -> decltype(b<a?a:b) {
    return b<a?a:b;
}

template<typename T1,typename T2>
auto max3(T1 a,T2 b) -> decltype(true?a:b) {
    return b<a?a:b;
}

//3.用common_type
template<typename T1,typename T2>
typename std::common_type<T1,T2>::type max4(T1 a,T2 b) {
    return b < a ?a:b;
}

int main() {
    auto v1 = max1(9,10.1);
    std::cout<<"v1:"<<v1<<std::endl;
    
    auto v2 = max2(9,10.1);
    std::cout<<"v2:"<<v2<<std::endl;
    
    auto v3 = max3(9,10.1);
    std::cout<<"v3:"<<v3<<std::endl;
    
    auto v4 = max4(9,10.1);
    std::cout<<"v4:"<<v4<<std::endl;
}

非类型模板参数

模板参数除了类型参数,可以使用非类型参数,如下,类模板Stack的大小就是非类型参数

template<typename T,std::size_t Maxsize>
class Stack {
private:
    std::array<T,Maxsize> elems;
...
};

显示指定类型和值 Stack<int,20>

如下定义了一个非类型参数的函数模板,结合std::transform的使用示例:

#include <iostream>
#include <algorithm>
#include <vector>
template<int val,typename T>
T addValue(T x) {
    return x + val; 
}

int main() {
    std::vector<int> s{1,2,3,4,5,6};
    std::vector<int> d;
    std::back_insert_iterator<std::vector<int>> bIt(d);
    std::transform(s.begin(),s.end(),bIt,addValue<5,int>);

    for(auto i:d) {
        std::cout<<i<<std::endl;
    }
}
非类型模板参数的限制

非类型模板参数,它们只能是:

  • constant integral values(包括枚举)
  • pointers to object/functions/members
  • lvalue references to objects or functions
  • std::nullptr_t

见如下例子:

#include <iostream>
class MyClass {
public:
    MyClass(int v):_v(v) {

    }

    void printV() {
        std::cout<<"v:"<<_v<<std::endl;
    }
private:
    int _v = 0;
};

enum enTest {
    ONE = 0,
    TWO = 1
};

//double类型不行
template <double v1>
double process(double v) {
    return v*v1;
}

//string类型不行
template <std::string s>
void process1() {
    std::cout<<s<<std::endl;
}

//string的左值引用可以
template <std::string& s>
void process11() {
    std::cout<<s<<std::endl;
}

//整型可以
template <int v>
void process2() {
    std::cout<<v<<std::endl;
}

//对象不行
template <MyClass c> 
void process3() {
    c.printV();
}

//对象的左值引用可以
template <MyClass& c>
void process33() {
    c.printV();
}

//对象的指针可以
template <MyClass* c>
void process333() {
    c->printV();
}

//枚举可以
template <enTest e>
void process4() {
    std::cout<<e<<std::endl;
}

int main() {
    process4<enTest::ONE>();
}

传入可调用对象

可以是函数,函数指针,函数对象,lambad。但是无法传入类的成员函数,需要通过std::invoker(C++17)来支持。

template<typename Iter,typename Callable>
void foreach(Iter current,Iter end,Callable op) {
    while(current != end) {
        op(*current);
        ++current;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mo4776

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值