C++primer 薄片系列之模板

非类型模板参数

template<int t1>
void test(const int(&p)[t1]) //非类型模板参数
{
    for(auto i = 0; i < t1; i++)
    {
        std::cout << p[i] << " ";
    }
    std::cout <<std::endl;
}


int main()
{
    int p[10] = {
   
   1,2,3,4,5,6};
    test<10>(p);
    return 0;

}

模板类成员函数

template<typename T>
class Test
{
public:
    void test();
};
template<typename T>
void Test<T>::test() //指明模板类型参数Test<T>
{
    std::cout << "1111111111111" << std::endl;
}

int main()
{
    auto s = new Test<int>();
    s->test();
}

定义在模板内内部的成员函数,被隐式定义为内联函数.
当使用模板类型时,必须提供模板实参,但是在类模板自己的作用域中,可以直接使用类型名,不需要实参.

template<typename T>
class Test
{
public:
    Test& operator= (Test & t)//内联. 同时这里返回类型Test<T> 被简写Test,编译器自动推断
    {
        std::cout << "1111111111111" << std::endl;
        return *this;
    }
    Test & operator++();
};
template<typename T>
Test<T> & Test<T>::operator++()//这里返回实例化的类型.必须给定模板实参
{
    Test ret = *this;//这里函数体内,已经进入类的作用域,所以不必指定模板实参
    ...
}

类模板和友元

类模板含有一个非模板友元时,则友元被授权可以访问所有模板的实例.如果友元自身是模板,类可以授权所有友元模板实例,也可以只授权特定实例.
--类成员函数只有被调用时才会被实例化

template <typename> class B;

template<typename T>
bool operator==(const B<T> &, const B<T> &);

template <typename T>class B
{

    //friend class A<T>;
    friend bool operator==<T>(const B<T> &, const B<T> &);//这里operator==<T> 意味该友元函数是模板友元
    T s;
    void test(const B<T> &s)
    {
        std::cout << s << " " << s.s << std::endl;// 这个代码有个bug, 对象名和对象内成员名冲突,但是如果这个函数没有被调用,编译器不会实例化这个函数,也就不会检测这个错误
    }
public:
    B(T k)
    {
        s=k;
    }
};
template<typename T>
bool operator==(const B<T>& a, const B<T> & b)//这里按照普通模板函数进行定义
{
    std::cout << a.s << " " << b.s << std::endl;
    return false;
}


int main()
{
    B<int> s1(10);
    B<int> s2(5);
    s1==s2;
    s1.test(s2);//调用这个函数,编译报错,注释这句话,则没有错误 
    return 0;

}
template<typename T> class Pal;

template<typename T> class C2;
class C
{
public:
    C(int cc):c(cc){}
    friend class Pal<C>;//这种需要前置声明
    template <typename T> friend class Pal2;//pal2不需要前置声明,因为这句话声明足够清晰了
private:
    int c;
};
template<typename T> class Pal
{
public:
    Pal(T ii):i(ii)
    {

    }
    int get()
    {
        return i.c;//访问C中的变量,因为Pal<c> 是C的友元
    }
    void getC2(C2<T>);
private:
    T i;
};
template<typename T> class Pal2
{
public:
    Pal2(T ii):i(ii)
    {

    }
    int get(C x)
    {
        return x.c;//Pal2任意参数的实例化都是C的友元,故可以访问C的私有对象
    }
    template<typename X>void getC2(C2<X> &);//Pal2的任意实例都是C2的每个实例的友元
private:
    T i;
};

template<typename T> class C2
{
    friend class Pal<T>;
    template<typename X> friend class Pal2;
    friend class Pal3;
public:
    C2(T aa):a(aa){}
private:
    T a;
};

template<typename T>
void Pal<T>::getC2(C2<T> k)
{
    std::cout << k.a << std::endl;//Pal<T>是C2<T>的友元,所以可以访问其私有变量
}

template<typename T>
template<typename X>//这里双层类型应该采用这种写法
void Pal2<T>::getC2(C2<X> &k)
{
     std::cout << k.a << std::endl;//Pal<T>是C2<T>的友元,所以可以访问其私有变量
}
class Pal3
{
public:
    Pal3(){}
    void test(C2<int> s)
    {
        std::cout << s.a << std::endl;//Pal3 是 C2 任意参数的实例化对象的友元函数,所以可以访问其私有成员
    }
};

int main()
{
    C c(66);
    Pal<C> pc(c);
    std::cout << pc.get() << std::endl;
    C2<int> c2(999);
    Pal3 p3;
    p3.test(c2);
    Pal<int> pi(55);
    C2<int> ci2(56);
    pi.getC2(ci2);

    Pal2<int> pc2(5);
    std::cout << pc2.get(c) << std::endl;
    Pal2<double> pc3(5.5);
    pc2.getC2(c2);
    pc3.getC2(c2);
    return 0;

}

模板类型别名

template<typename T> using twin = std::pair<T,T>;
int main()
{
    twin<int> k = {
   
   1,1};
}

模板中静态成员的定义方式

template<typename T>
class M
{
private:
    static int a;
};
template<typename T>
int M<T>::a = 10;

默认模板实参

template<typename T, typename K = int>//默认模板实参
int test(T a, K b)
{
    return a+b;
}
template<typename T = int>//类模板默认实参
class Number
{
public:
    Number(T ii):i(i){}
private:
    T i;
};
int main()
{
  std::cout <<   test<int,double>(10,20) << std::endl;
  std::cout <<   test<int>(10,20) << std::endl;
  Number<double> n1(10.4);
  Number<> n2(11);// 尖括号里面没有任何参数,视为使用默认实参
}

一个类(无论是不是模板类)的成员函数可以是模板函数,这样的成员被称为成员模板.
成员模板不能是虚函数.(!!!!)

template<typename T>
class Number
{
public:
    Number(T ii):i(i){}
    template<typename X> void test(X &);//成员模板
private:
    T i;
};
template<typename T>
template<typename X>
void Number<T>::test(X & s)
{
    std::cout << "this is a test" << std::endl;
}
int main()
{
    Number<int> s(10);
    double k=10.0;
    s.test(k);
}

控制实例化

当两个或多个源文件使用了相同的模板并提供相同的模板函数时,每个文件就会有一个该模板的一个实例.在大系统中,在多个文件中实例化相同的模板开销很大.可以采用显示实例化来避免开销.

//test.h
#ifndef TEST_H
#define TEST_H

template<typename T>
class Test
{
    public:
        Test()
        {

        }
};
template class Test<int>;//定义,实例化

#endif // TEST_H

//I.h
#ifndef I_H
#define I_H

#include "Test.h"
extern template class Test<int>;//实例化出现在其他位置
class I
{
    public:
        I()
        {
            class Test<int> s;
            s  = {};
        }
        virtual ~I()
        {

        }
};

#endif // I_H
//main.cpp
extern template class Test<int>;//实例化出现在其他位置
int main()
{
    I ii{};
    Test<int> tt{};
}

标准库类型转换

#include<type_traits>
template<typename T>
auto fcn(T a) -> typename std::remove_reference<decltype(*a)>::type
{
    return *a;
}

template<typename T>
auto fcn2(T a) -> typename std::remove_pointer<decltype(*a)>::type
{
    return *a;
}

int main()
{
    int *s = new int(10);
    std::cout << fcn2(s) << std::endl;
    std::vector<int> k = {
   
   20};
    std::cout <<   fcn<std::vector<int>::iterator>(k.begin()) << std::endl;

}

模板实参推断和引用

如果函数参数的类型是T &, 只能传递给其一个左值
如果函数参数的类型是const T &, 则可以传递给它一个对象(const 或者非const),一个临时对象或者一个字面值常量

template<typename T>
void fff1(T& k)
{
    std::cout << k << std::endl;
}
template<typename T>
void fff2(const T& k)
{
    std::cout << k << std::endl;
}
int main()
{
    int k = 10;
    fff1(k);
    fff2(k);
  //  fff1(5); //不能将字面值常量绑定给一个左值
    fff2(5);//能将字面值常量绑定给一个const T&
}

引用折叠和右值引用参数

通常不能将一个左值赋值给右值引用.

void f(int && s)
{

}
int main()
{
    int i = 10;
    f(i);//这里报错,因为i 是个左值

那么在模板类型函数中,有了两个个例外规则.
规则一: 当将一个左值传递给函数的右值引用参数,并且右值引用指向模板类型参数,编译器推断模板类型参数为实参的左值引用类型

template<typename T>//模板类型参数为T
void f(T &&)//函数f的参数为右值引用,且该右值引用指向模板类型参数T
{
}
int i = 10;
f(i);//此时将i传递给f做参数时,编译器认为模板类型参数为T = int &, 而不是T = int(!!!)

T = int & 时,f 的参数T &&就被转换为 int& &&.通常不能定义引用的引用.为了避免int& &&被看成左值引用的右值引用,由此产生了引用折叠
规则二: c++语言对于引用折叠做出了规定,如果我们间接创建引用的引用,则这些引用会形成折叠.折叠的结果是,对于给定的类型X:
—- X& &,X& &&, X&& & 均被折叠成X &
—- X&& &&被折叠成X &&
简单来说就是,除了右值引用的右值引用被定义为右值引用外, 左值引用的右值引用,右值引用的左值引用, 左值引用的左值引用都被定义成左值引用

template<typename T>
void f(T && s)//f接受一个模板类型参数的右值引用.
//调用f(i)时,按规则一,左值i 被传递给函数的右值引用参数,此时编译器推断模板类型参数T = int &int &.
//调用f(10)时,按规则二,int && &&折叠成 int &&, 此时T=int 
{
    T k = s;
    k = 5;
    std::cout << s << " " << k << std::endl;
    //f(i)的输出结果是5 5.因为k 的类型T=int &, k 是s 的左值引用,所以k 变了,s 也变了
    //f(10)的输出结果是10 5.因为k 的类型T=int, 所以k 变了,s 不变

}
int main()
{
    int i = 5;
    f(i); //将i 传递给f
    f(10);
}

template<typename T> void f(T&&);//绑定非const 的右值
template<typename T> void f(const T&);//绑定左值或者const 右值

右值引用可以自由修改所绑定的临时变量的值

    int &&s = 10;
    int m = 20;
    s = 20;

!!!!!!!!右值引用变量本身是个左值

int&&x=0;
int&&i = x; //错误

右值引用赋值右值引用

    int &&temp = 0;
    int &&i = 1;
    i = temp;

转发

模板函数在实例化调用过程中,如果模板类型参数不是引用的话,那么就是函数的参数就是按值传递。

template<typename T>
void testx(T s)
{
    s = 20; // 将值修改成20
}
int main()
{
    int p = 10;
    testx(p);
    std::cout << p << std::endl;//输出10
}

模板类型参数既可以是普通类型,也可以是可调用对象。

template<typename T>
void testx(T s)
{
    s();
}
void f()
{
    std::cout << "test" << std::endl;
}

int main()
{
    typedef void(*T)();
    T p = f;
    testx(p);//模板类型参数T被实例化为void(*func)()
}

上面演示了可调用对象的无参版本,假如我们希望可调用对象接受参数,并且可调用的对象的参数是按引用传递的

template<typename T,typename K>
void testx(T s, K k)
{
    s(k);
}
void f(int & k)
{
    k = 10;//假设需要改变参数的值 
}

int main()
{
    typedef void(*T)(int &);
    T p = f;
    int m = 0;
    testx(p,m);//模板类型参数T被实例化为void(*func)(int &)
    std::cout << m << std::endl;//!!!输出0
}

这里调用模板后,m并没有按我们预期的发生变化,这是为什么呢?
事实上,依据之前说,我们模板参数类型K是按值传递的。
testx(p,m)在执行过程中,
模板被实例化成

testx(void(*func)(int &), int t)
func 的引用参数绑定到了t,而不是testx(p,m)中的m。从而不会按我们预期的改变值。

std::forward 完美的解决了这个问题
std::forward < Type > 会返回 Type&&. 结合引用折叠,左引用折叠后还是左引用,右引用折叠后成右引用.从而保留引用的原始类型

template<typename T, typename K>
void testx(T s, K &&k)
{
    s(std::forward<K>(k));
}
void f(int & k)
{
    k = 10;//假设需要改变参数的值 
}
void g(int &&k)
{
    k = 13;
}

int main()
{
    int m = 10;
    testx(f, m);//f接受一个左值作为参数
    testx(g, 12);//g接受一个右值作为参数
}

函数重载匹配。如果同样匹配的模板和非模板函数,优先匹配非模板函数

template<typename T>
void test(T t)
{
    std::cout << "test " << std::endl;
}

void test(int t)
{
    std::cout << "test 2" << std::endl;
}

int main()
{
    int m = 10;
    test(m);//输出test2
}

可变参数模板

template<typename ...Args>
void foo(Args ... r)
{

    std::cout << sizeof...(r) << std::endl;
}


template<typename T>
std::ostream &test(std::ostream &os, T t)
{
    return os << t;
}

template<typename T,typename ...Args>
std::ostream& test(std::ostream &os, const T& t, const Args& ... r)//扩展Args
{
    os << t << ",";
    return test(os, r...);//扩展r
}

int main()
{
    foo(10,2.5,"ss");
    test(std::cout, 1, "2.5", "dssds");

}

还可以来个更复杂点的

std::string debug(std::string t)
{

    return t;
}

template<typename T>
std::string debug(const T&t)
{
    return debug(std::string(t));
}

template<typename T>
std::ostream &test(std::ostream &os, T t)
{
    return os << t;
}

template<typename T,typename ...Args>
std::ostream& test(std::ostream &os, const T& t, const Args& ... r)
{
    os << t << ",";
    return test(os, debug(r)...);
}
template<typename ...Args>
std::ostream & testA(std::ostream &os, Args... r)
{
    return test(os, debug(r)...);//调用test扩展,并对每个元素先进行debug转换
}
int main()
{
    testA(std::cout, "2.5", "dssds");

}

转发和扩展结合起来后,我们就可以实现较为庞大的函数了

template<class ... Args>
void emplace(Args&& ... args)
{
    test(std::forward<Args>(args...));//假设test函数需要保留emplace的参数原始类型.和上一段代码相比,其实就是用std::forward<Args> 替换掉了debug
}

模板特例化

模板的定义对特定类型不适用,所以需要特例化

template<typename T>
int test(const T& s)
{
    return s.size();
}
template <>
int test(const char* const &s)
{
    return 10;
}

int main()
{
    std::string  k = "111";
    std::cout << test(k) << std::endl;

    const char * s= "111";
    std::cout << test(s) << std::endl;
}

当我们特例化一个函数模板时,必须为原模板中的每个模板参数提供实参.为了指明我们正在实例化一个模板,应该使用关键字template<>指明

类模板特例化
类似于函数模板特例化,唯一要求必须在原模板定义所在的命名空间

namespace AA
{
    template<typename T>
    class M
    {
     public:
        M()
        {
            std::cout << "000000" << std::endl;
        }
    };
};
struct Data
{
};
namespace AA
{
    template<>
    class M<Data>
    {
    public:
        M()
        {
            std::cout << "111111" << std::endl;
        }
    };

}

int main()
{
   AA::M<int> t;
   AA::M<Data> s;
}

部分特例化

namespace AA
{
    template<typename T>
    class M
    {
     public:
        void test(T k)
        {
            std::cout << "orignal" << std::endl;
        }
    };
};
namespace AA
{
    template<>//都是template <>  打头
    void M<int>::test(int k)//针对M<int>的成员test做特殊处理
    {
        std::cout << "part special" << std::endl;
    }
}
int main()
{
   AA::M<int> t;
   AA::M<double> r;
   t.test(10);
   r.test(10);
}

猜你喜欢

转载自blog.csdn.net/jxhaha/article/details/78532752