C++泛型编程类模板,变量模板,别名模板,CSATD

类模板

类模板声明、实现与使用

声明:
template <typename T>
class Stack {
private:
  std::vector<T> elems; // elements
public:
  void push(T const &elem); // push element
  void pop();               // pop element
  T const &top() const;     // return top element
  bool empty() const {      // return whether the stack is empty
    return elems.empty();
  }
};
实现:
template <typename T>
void Stack<T>::push(T const &elem) {
  elems.push_back(elem); // append copy of passed elem
}

template <typename T>
void Stack<T>::pop() {
  assert(!elems.empty());
  elems.pop_back(); // remove last element
}

template <typename T>
T const &Stack<T>::top() const {
  assert(!elems.empty());
  return elems.back(); // return copy of last element
}
使用:
int main() {
  Stack<int> intStack;            // stack of ints
  Stack<std::string> stringStack; // stack of strings

  // manipulate int stack
  intStack.push(7);
  std::cout << intStack.top() << '\n';

  // manipulate string stack
  stringStack.push("hello");
  std::cout << stringStack.top() << '\n';
  stringStack.pop();
}

注意

  • 在类声明内的构造函数、拷贝构造函数、析构函数、赋值等用到类名字的地方,可以将Stack<T>简写为Stack,例如:
template<typename T>
class Stack {
  ...
  Stack (Stack const&);                           // copy constructor
  Stack& operator= (Stack const&);      // assignment operator
...
};

但是在类外,还是需要Stack<T>:

template<typename T>
bool operator== (Stack<T> const& lhs, Stack<T> const& rhs);

注意: 拷贝构造函数(构造函数)在类内声明类外定义,类名是不需要加上模板名的。因为构造函数的定义就是函数名就是类名

例子

#include <iostream>
#include <vector>
#include <assert.h>
using namespace std;
namespace detail
{
    template <typename T>
    class Stack
    {
    private:
        std::vector<T> elems; // elements
    public:
        void push(T const &elem); // push element
        void pop();               // pop element
        T const &top() const;     // return top element
        bool empty() const
        { // return whether the stack is empty
            return elems.empty();
        }
        Stack() = default; //
        Stack(Stack const &stack);  //拷贝构造函数
        Stack &operator=(Stack const &stack);
    };
    template <typename T>
    void Stack<T>::push(T const &elem)
    {
        elems.push_back(elem); // append copy of passed elem
    }
    template <typename T>
    void Stack<T>::pop()
    {
        assert(!elems.empty());
        elems.pop_back(); // remove last element
    }

    template <typename T>
    T const &Stack<T>::top() const
    {
        assert(!elems.empty());
        return elems.back(); // return copy of last element
    }
    template <typename T>
    Stack<T>::Stack(Stack<T> const &stack)
    {
        ;
    }
    /// 注意构造函数名字的stack后面不要加<T>,构造函数的定义就是类名作为函数名

    // Stack<T>::Stack<T>(Stack<T> const &stack)
    // {
    //     ;
    // }
    template <typename T> //重载赋值运算符
    Stack<T> &Stack<T>::operator=(Stack<T> const &stack) 
    {
    }
    /// 这里不是构造函数,所以用到stack 的地方都添加了<T>

}

int main(int argc, char **argv)
{

    detail::Stack<int> intStack;            // stack of ints
    detail::Stack<std::string> stringStack; // stack of strings

    // manipulate int stack
    intStack.push(7);
    std::cout << intStack.top() << '\n';

    // manipulate string stack
    stringStack.push("hello");
    std::cout << stringStack.top() << '\n';
    stringStack.pop();
}

注意: 拷贝构造函数(构造函数)在类内声明类外定义,类名是不需要加上模板名的。因为构造函数的定义就是函数名就是类名


类模板的参数推导

Deduction Guides

我们可以使用Deduction Guides来提供额外的模板参数推导规则,或者修正已有的模板参数推断规则。

Stack(char const*) -> Stack<std::string>;

Stack stringStack{"bottom"};           // OK: Stack<std::string> deduced since C++17

例子

#include <iostream>
using namespace std;
namespace detail
{
    template <typename T>
    class A
    {
    public:
        A(T x, T y)
        {
            cout << "A" << x << " " << y << endl;
        }     
    };
}
// 自定义推断指南
// https://blog.csdn.net/qq_38158479/article/details/122902315
namespace detail2
{
    template <typename T>
    class B
    {
    public:
        T mb;
        T mbb;
    };
    template <typename T>
    B(T) -> B<T>;  //自定义推断指南
     template <typename T>
    B(T, T) -> B<T>; //自定义推断指南

}
namespace detail3
{
    template <typename T>
    class C
    {
    public:
        T mc;
    };
    

}

int main(int argc, char **argv)
{
    // C17 可以推断类模板了
    detail::A a(15, 16); //使用的是编译器根据构造函数 自己合成的推断指南
    detail2::B b{18};   //使用的是自定义的推断指南,类B没有提供构造函数,所以不会合成推断指南。需要自己提供
     detail2::B bb{19, 25};
	 detail3::C c{18};   // error 没有提供构造函数,也没有提供推断指南。所以会报错
}

类模板实参推导

  • C++17支持类模板类型推导(class template argument deduction,在下面的文章中,我叫做CTAD)。

  • 而我们在很久之前就有了template argument deduction,但是只能用于函数,这多少有点不公平。

  • 此篇博客的内容来自cppcon2018_CTAD 。

//before C++17
std::pair<int, string> p1(3, "string");
auto p2 = make_pair(3, "string");
//deduction pair<int, const char*>

//C++17 or late
std::pair p3(3, string("hello")); // nice !

CTAD是如何工作的?

  • CTAD工作的具体细节是很复杂的。而且有些细节是给编译器实现者使用的。
  • 这里,我将介绍CTAD工作的最重要的两步。
template <class T, class U>
struct pair{
	T first;
	U second;
	pair(const T& first_, const U& second_)
	:first(first_), second(second_)
	{}

	pair(T&& first_, U&& second_)
	:first(std::forward<T>(first_))
	,second(std::forward<U>(second_))
	{}
	//...
};

std::pair p(3, string("hello")); //how does it work ?

上面是粗略的std::pair的实现,我们使用它来进行讲解。

第一步:

  • 当编译器看到你尝试去初始化一个p,编译器又看见了pair是一个模板的名字。但是你没有显式传入模板实参,而且pair没有默认值,所以编译器需要CTAD。
  • 编译器会去查看pair的构造函数,它会假装构造函数是普通的函数模板,像下面这样,(函数模板是可以推断参数类型的)
	template <class T, class U>  //来自pair类
	pair(const T& first_, const U& second_)
	:first(first_), second(second_)
	{}
	
	template <class T, class U> //来自pair类
	pair(T&& first_, U&& second_) //右值引用,不是转发引用
	:first(std::forward<T>(first_))
	,second(std::forward<U>(second_))
	{}

  • 编译器会假装synthesis(合成,函数重载的术语)两个上面的函数,将类的模板参数列表加到构造函数的头部。

  • 然后编译器就使用模板实参推导,overload resolutions等一系列方法,去分辨函数重载中最合适的那一个。

  • 最终,匹配了第二个右值引用的pair,然后编译器就会推导出p的类型为pair< int, string >。

第二步:

  • 注意,第一步中,没有进行任何的实例化,仅仅是推导出模板参数。
  • 实例化发生在第二步。
  • 编译器现在有了p的类型,pair<int, string >,然后就可以调用第二个构造函数实例化出该对象。

参考:

https://zhuanlan.zhihu.com/p/338652651

http://t.csdn.cn/jQsYQ

https://zh.cppreference.com/w/cpp/language/class_template_argument_deduction

https://www.bilibili.com/video/BV1kW41117uw/?p=128&vd_source=9d5a4bc24b5e6d9bbf4a20a5dcd10b18

https://www.youtube.com/playlist?list=PLHTh1InhhwT6V9RVdFRoCG_Pm5udDxG1c

类模板的泛化; 类模板的偏特化; 类模板的全特化; 静态成员变量的全特化; 普通成员变量的全特化

#include <iostream>
using namespace std;
namespace detail
{
    // 类模板的全特化
    template <typename T, typename U>
    class TC
    {
    public:
        TC() { cout << "TC 的泛化版本" << endl; }
        void func() { cout << "func的泛化版本" << endl; }
    };
    template <>
    class TC<int, int> // 特化版本类名之后有<>
    {

    public:
        TC() { cout << "TC<int,int> 的特化版本" << endl; }
        int func()
        {
            cout << "func的特化版本" << endl;
            return 0;
        }
    };
}
namespace detail2
{
    template <typename T, typename U>
    class TC
    {
    public:
        TC() { cout << "TC 的泛化版本" << endl; }
        void func() { cout << "func的泛化版本" << endl; }

        static int m_age; // 静态成员变量的声明
    };

    template <typename T, typename U>
    int TC<T, U>::m_age = 18; // 静态变量的定义

    template <>
    int TC<int, double>::m_age = 80; // 静态变量的全特化
    template <>
    void TC<string, double>::func()
    {
        cout << "普通成员函数TC<double,int>::functest1的全特化" << endl;
        return;
    }
    // template <>
    // class TC<string, double>
    // {
     如果进行了普通成员函数的全特化,或者是静态成员变里的全特化
    // ,那么,就无法用这些全特化时指定的类型来对整个类模板进行全特化
    // };
    // template <>
    // class TC<int, double>
    // {

    /// 如果进行了普通成员函数的全特化,或者是静态成员变里的全特化
    // ,那么,就无法用这些全特化时指定的类型来对整个类模板进行全特化
    // };
    template <>
    class TC<int, char>
    {
    }; // 这种特化由于类型不是  普通成员函数特化的<string ,douoble> 和 静态变量全特化的<int,double> ,所以是可行的
}
namespace detail3
{
    template <typename T, typename U>
    class TC
    {
    public:
        TC() { cout << "TC 的泛化版本" << endl; }
        void func() { cout << "func的泛化版本" << endl; }
    };

    // 范围上的偏特化
    template <typename T, typename U>
    class TC<const T *, const U *>
    {
    public:
        TC() { cout << "TC范围上 的偏特化版本" << endl; }
        void func() { cout << "func的范围偏特化版本" << endl; }
    };
    // 数量上的偏特化
    template <typename U>
    class TC<int, U>
    {
    public:
        TC() { cout << "TC 的数量上的偏特化版本" << endl; }
        void func() { cout << "func的数量偏特化版本" << endl; }
    };

}
int main(int argc, char **argv)
{

    // 调用泛化版本
    detail::TC<float, float> tc;
    tc.func();
    // 调用特化
    detail::TC<int, int> tc2;
    tc.func();
    // 调用普通成员函数的全特化
    detail2::TC<string, double> tc3;
    tc3.func();
    // 调用静态变量的全特化
    detail2::TC<int, double> tc4;
    cout << tc4.m_age;
    // 如果进行了普通成员函数的全特化,或者是静态成员变里的全特化
    // ,那么,就无法用这些全特化时指定的类型来对整个类模板进行全特化
    // 因为对普通成员函数的全特化,或者是静态成员变量的全特化 就是生成了对应的参数的类的全特化版本

    // 调用范围上的偏特化版本
    detail3::TC<const int *, const int *> b;
    b.func();
    // 调用数量上的偏特化版本
    detail3::TC<int, double> c;
    c.func();
}

类模板的默认参数

类型别名

非类型模板参数的使用


#include <iostream>
using namespace std;
namespace detail
{ // 默认版本的泛化
    template <typename T, typename U>
    class TC
    {
    public:
        TC()
        {
            cout << "泛化版本的构造函数" << endl;
        }
        void func()
        {
            cout << "泛化版本的函数" << endl;
        }
    };
#if 0
    template <typename T, typename U>
    class TC<T *, T *= int *>
    {
        // 类模板偏特化版本中的类型模板参数不可以有缺省值。
    public:
        TC()
        {
            cout << "范围特化版本的构造函数" << endl;
        }
        void func()
        {
            cout << "特化版本的函数" << endl;
        }
    };
    template <typename T>
    class TC<int, T = double>
    {
        // 类模板偏特化版本中的类型模板参数不可以有缺省值。
    public:
        TC()
        {
            cout << "数量特化版本的构造函数" << endl;
        }
        void func()
        {
            cout << "特化版本的函数" << endl;
        }
    };

#endif
}
namespace detail2
{
    // 常规缺省
    template <typename T, typename U = const int *>
    class TC
    {
    public:
        TC()
        {
            cout << "泛化版本的构造函数" << endl;
        }
        void func()
        {
            cout << "泛化版本的函数" << endl;
        }
    };

}
namespace detail3
{ // 后面的模板参数依特前面的模板参数
    template <typename T, typename U = T *>
    class TC
    {
    public:
        TC()
        {
            cout << "泛化版本的构造函数" << endl;
        }
        void func()
        {
            cout << "泛化版本的函数" << endl;
        }
    };

}
#if 0
namespace detail4
{
// 类型模板参数缺省值的规矩:如果某个模板参数有缺省值,那么从这个有缺省值的模板参数开始,后面的所有模板参数都得有缺省值。
    template <typename T=const double*, typename U>
    class TC
    {
    public:
        TC()
        {
            cout << "泛化版本的构造函数" << endl;
        }
        void func()
        {
            cout << "泛化版本的函数" << endl;
        }
    };

}

#endif
namespace detail5
{
    // 类模板声明中,缺省参数要先给右边参数提供缺省值,再给左边提供
    // template<typename T=int,typename T2>
    // class T ;
    // 在声明中指定缺省值
    // 声明1
    template <typename U, typename T, typename Z, typename Y, typename X = int>
    class TC;
    // 声明2
    template <typename U, typename T, typename Z, typename Y = double, typename X> // 这里的X 已经被指定默认参数了
    class TC;
    // 声明3
    template <typename U, typename T = U *, typename Z = const int *, typename Y, typename X = int> // 这里的X Y已经被指定默认参数了
    class TC;
    // 声明4
    template <typename U, typename T = double *, typename Z, typename Y, typename X = int> // 这里的X Y Z已经被指定默认参数了
    class TC;
    // 声明5
    template <typename U, typename T, typename Z, typename Y, typename X = int> // 这里的X Y Z T 已经被指定默认参数了
    class TC
    {
    };

}
namespace detail6
{
    template <typename U, typename T = double *, typename Z = int, typename Y = int, typename X = int> // 这里的X Y Z已经被指定默认参数了
    class TC
    {
    };
    // 方式一定义别名
    typedef TC<int, double, int, const int *, double> i_D_I_CI_D; // 定义类型别名
    // 方式二定义别名
    using i_D_I_CI_D2 = TC<int, double, int, const int *, double>;
}
namespace detail7
{
    template <typename T, typename U, size_t arrsize = 8>
    class TC
    {
    public:
        T m_aee[arrsize];
        void func();
    };

    template <typename T, typename U, size_t arrsize>
    void TC<T, U, arrsize>::func()
    {
        cout << "func" << endl;
    }
}
namespace detail8
{
    template <double VAT>
    double process(double v)
    {
        return v * VAT;
    }
    // error: a non-type template parameter cannot have type 'double'

    template <std::string name>
    class TemStr
    {
    public:
        std::string _str = name;
    };
    // error: a non-type template parameter cannot have type 'std::string'

    template <char const *name>
    class TemPtr
    {
    public:
        std::string _str = name;
    };
    // extern char const* sa = "test";//ERROR
    // error: non-type template argument of type 'const char *' is not a constant expression
    // extern char const sa[] = "test";//OK

    // 浮点和string直接不使用也会编译error.
    // 全局字符数组是一个外部链接对象,可以作为非类型模板参数。
    // 非类型模板参数类型是有限制的,通常是常整数,枚举值,或者指向外部链接对象的指针。 浮点数应为历史原因不能作为非类型模板参数,以后可能会支持。
    //     字符串文字是内部链接对象,类对象更不能作为非类型模板参数。
 //类类型不能作为非类型模板参数
class a{};
template<typename T, a size>
class myarray{};
}
int main(int argc, char **argv)
{
    // 类模板中的缺省参数
    detail2::TC<int> tc;

    tc.func();
    detail3::TC<double> tc2;
    tc2.func();
    // 声明中缺省
    detail5::TC<int, int, int, int, int> tc3;
    detail5::TC<int> tc4;

    detail5::TC<int, double> tc5;
    detail5::TC<int, double, float> tc6;
    detail5::TC<int, double, float, const int> tc7;
    // 使用类型别名
    detail6::i_D_I_CI_D tc7;

    detail6::i_D_I_CI_D2 tc8;
    // 非类型模板参数
    detail7::TC<int, int> tc9;
    for (size_t i = 0; i < 8; i++)
    {
        tc9.m_aee[i] = static_cast<int>(i);
    }
    cout << tc9.m_aee[7] << endl;
}

成员函数模板;模板类虚函数;模板虚函数;拷贝构造函数;拷贝构造函数模板;赋值运算符;赋值运算符模板

#include <iostream>
using namespace std;
namespace detail
{
    // 成员函数模板
    template <typename T>
    class TC
    {
    public:
        template <typename T2>
        T2 func(T2 arg1, T2 arg2) // 成员函数模板
        {
            return arg1 + arg2;
        }

        void func2() // 成员函数
        {
            cout << this->num1 << " " << this->num2 << endl;
        }
        int num1 = 300;
        double num2 = 1.1;
    };
}
namespace detail2
{
#if 0 // 模板类虚函数
    template <class T>
    class A
    {
    public:
        virtual ~A() {}
        virtual void foo(T &t) {}
    };

    template <class T, class R>
    class B : public A<T>
    {
        R *r;

    public:
        void foo(T &t) override {}
    };

//     /类模板中可以有普通的虚成员函数(虚函数),这并没有什么问题。大家都知道,普通成员函数如果不被调用的情况下不会被实例化出来。
// /但是,对于虚函数,不管是否调用,编译器都会把他实例化出来,因为编译器要创建虚函数表t1,该表中的每个具体表项都对应一个
// /虚函数地址,所以编译器必然得把所有虚函数都实例化出来。
#endif

#if 0
    class A // 模板虚函数
    {
    public:
        virtual ~A() {}
        template <class T>
        virtual void foo(T &t) {}
    };

    class B : public A
    {
    public:
        template <class T>
        void foo(T &t) override {}
    };

    // 解析虚函数调用的最流行方法是使用表(vtable"),其中每个虚函数都映射到表中的一个索引。这
    // 或多或少需要您知道表的大小。
    // 使用模板,将在不同的模块中根据需要创建新功能。然后,您要么必须说服链接器在计算出函数的
    // 最终数量后构建表,要么使用某种运行时结构在运行时搜索可用函数。
    // 在许多系统上,链接器是操作系统的一部分,对C++一无所知,因此该选项受到限制。运行时搜索
    // 当然会对性能产生负面影响,也许对所有虚函数都是如此。
    // 因此,最终决定不值得将虚拟模板引入语言中。

    // 来自C++模板完整指南:
    // 成员函数模板不能声明为虚拟的。施加此约束是因为虚函数调用机制的通常实现使用固定大
    // 小的表,每个虚函数有一个条目。但是,直到整个程序被翻译后,成员函数模板的实例化次
    // 数才固定下来。因此,支持虚拟成员函数模板需要在C++编译器和链接器中支持一种全新
    // 的机制。相比之下,类模板的普通成员可以是虚拟的,因为在实例化类时它们的数量是固定
    // 的
    //     C++父的说法:如果允许虚函数模板,则每次有人用新的参数类型调用该虚函数模板时,就必须给对应的虚函数表再增加一项,这意味
    // //只有链接程序才能去构造虚函数表并在表中设置有关函数,因此,成员函数模板绝不能是虚的。
#endif

}
namespace detail
{
    // 类(包括模板类)构造函数是真实的构造函数;然而模板构造函数,其实质是模板函数。
    // 两者不能混为一谈。在一个模板类中,构造函数和模板构造函数同时存在时,优先调用构造函数。
    // 只有当确切符合模板构造函数的接口时,才调用模板构造函数。编译器永远不会把模板构造函数视为构造函数,
    // 即使客户没有自己定义拷贝构造函数,编译器也会生成一个默认的拷贝构造函数(而不会调用拷贝构造函数模板),这种情况同样存在于拷贝赋值函数和模板拷贝赋值函数。
    // 请看下面的例子:

#include <iostream>
    using namespace std;

    template <typename T>
    class TempClass
    {
    public:
        T d;

        TempClass<T>(T _d = 0) : d(_d) { cout << "This is TempClass Constructor1. " << endl; };

        template <typename O> // 模板拷贝构造函数
        TempClass<T>(const TempClass<O> &_tmp) : d(_tmp.d)
        {
            cout << "This is a template constructor, not a TempClass Constructor." << endl;
        };
        // template <typename O> // 模板拷贝构造函数
        //     TempClass<T>(TempClass<O> &_tmp) : d(_tmp.d)
        //     {
        //         cout << "This is a template constructor, not a TempClass Constructor." << endl;
        //     };
    };

}
namespace detail2
{
    // 类(包括模板类)构造函数是真实的构造函数;然而模板构造函数,其实质是模板函数。
    // 两者不能混为一谈。在一个模板类中,构造函数和模板构造函数同时存在时,优先调用构造函数。
    // 只有当确切符合模板构造函数的接口时,才调用模板构造函数。编译器永远不会把模板构造函数视为构造函数,
    // 即使客户没有自己定义拷贝构造函数,编译器也会生成一个默认的拷贝构造函数(而不是去调用拷贝构造函数模板),这种情况同样存在于拷贝赋值函数和模板拷贝赋值函数。
    // 请看下面的例子:

#include <iostream>
    using namespace std;

    template <typename T>
    class TempClass
    {
    public:
        T d;
        // 两个构造函数,其中第二个是拷贝构造函数
        TempClass<T>(T _d = 0) : d(_d) { cout << "This is TempClass Constructor1. " << endl; };
        TempClass<T>(TempClass<T> &_tmp) : d(_tmp.d) { cout << "This is TempClass Constructor2. " << endl; };

        template <typename O> // 模板构造函数
        TempClass<T>(TempClass<O> &_tmp) : d(_tmp.d)
        {
            cout << "This is a template constructor, not a TempClass Constructor." << endl;
        };
    };

}
namespace detail3
{
// 在拷贝构造函数和拷贝构造函数中不使用const 的情况
#include <iostream>
    using namespace std;

    template <typename T>
    class TempClass
    {
    public:
        T d;
        // 两个构造函数,其中第二个是拷贝构造函数
        TempClass<T>(T _d = 0) : d(_d) { cout << "This is TempClass Constructor1. " << endl; };
        // TempClass<T>(TempClass<T> &_tmp) : d(_tmp.d) { cout << "This is TempClass Constructor2. " << endl; };

        template <typename O> // 模板构造函数
        TempClass<T>(TempClass<O> &_tmp) : d(_tmp.d)
        {
            cout << "This is a template constructor, not a TempClass Constructor." << endl;
        };
    };
}
namespace detail4
{
    template <typename T1>
    // 拷贝赋值运算符
    class A
    {
    public:
        template <typename U>
        A<T1> &operator=(const A<U> &other)
        {
            cout << "operator=(const A<T1> &other) 拷贝赋值运算符执行了! " << endl;
            return *this;
        }

    public:
        T1 t1;
        A(T1 t1) : t1(t1) { cout << "t1" << endl; };
        A() = default;
    };
}
int main(int argc, char **argv)
{
    detail::TC<int> tc;
    //   调用成员函数模板
    cout << tc.func(100, 200) << endl;

    // 调用成员函数
    tc.func2();
    // 1类模板中的成员函数,只有源程序代码中出现调用这些成员函数的代码时,这些成员函数才会出现在一个实例化了的类模板中
    // 2类模板中的成员函数模板,只有源程序代码中出现调用这些成员函数模板的代码时,这些成员函数模板的具体实例才会出现在一个实例化了的类中

    detail::TempClass<int> a;

    detail::TempClass<int> b(a); // detail中 没有拷贝构造函数,但也不会去调用拷贝构造函数模板,而是生成默认的拷贝构造函数来调用

    detail2::TempClass<double> c(); // 调用模板拷贝构造函数
    cout << "================================================================" << endl;
    detail2::TempClass<int> aa;
    detail2::TempClass<int> bb(aa); // 当类型相同时候调用拷贝构造函数(调用detail2 中已有的),即使在该模板类中用户没有自定义该函数,编译器也会生成一个默认拷贝构造函数。因为编译器永远不会认为一个模板构造函数是一个构造函数

    detail2::TempClass<double> cc(aa); // 当类型不同时,调用模板拷贝构造函数

    /**
300
300 1.1
This is TempClass Constructor1.
This is a template constructor, not a TempClass Constructor.
================================================================
This is TempClass Constructor1.
This is TempClass Constructor2.
This is a template constructor, not a TempClass Constructor.
    */
    cout << "===============detail3===============" << endl;
    detail3::TempClass<int> aaa;
    detail3::TempClass<int> bbb(aaa);

    detail3::TempClass<double> ccc(aaa);
    // 当不使用const 的时候,类型相同和类型不同都会调用拷贝构造函数模板,没有拷贝构造函数,就会去调用拷贝构造函数模板
    /*
    ===============detail3===============
This is TempClass Constructor1.
This is a template constructor, not a TempClass Constructor.
This is a template constructor, not a TempClass Constructor.


    */
    cout << "===========detail4===============" << endl;
    detail4::A<int> a4(15);
    detail4::A<int> b4;
    b4 = a4; // 和拷贝构造函数相同,类型相同调用默认生成的赋值运算符,而不是赋值运算符模板
    cout << "b::" << b4.t1 << endl;

    detail4::A<double> c4; // 类型不同时,调用赋值运算符模板
    c4 = a4;
}

成员函数的泛化版本;成员函数的特化版本;类外定义的全特化版本; 函数模板的嵌套;类模板的嵌套

#include <iostream>
using namespace std;
#if 0
namespace detail
{
    template <typename T>
    class TC
    {
    public:
        template <typename T1, typename T2>
        void func(T1 a, T2 b)
        {
            cout << "func()泛化版本" << endl;
        }
        template <typename T2>
        void func(int a, T2 b)
        {
            cout << "func()偏特化版本" << endl;
        }
        template <>  
        void func(int a, double b)
        {
            cout << "func()全特化版本" << endl;
        }
        TC(int args, int args2) : args(args), args2(args2){};
        int args, args2 = 0;
    };
}
#endif
#if 0
namespace detail2
{   //类模板之外,定义成员函数的全特化版本
    template <typename T>
    class TC
    {
    public:
        template <typename T1, typename T2>
        void func(T1 a, T2 b);
        template <typename T2>
        void func(int a, T2 b);
        template <>
        void func(int a, double b);
        TC(int args, int args2) : args(args), args2(args2){};
        int args, args2 = 0;
    };
    template <typename T>
    template <typename T1, typename T2>
    void TC<T>::func(T1 a, T2 b)
    {
        cout << "func()泛化版本" << endl;
    }
    template <typename T>
    template <typename T2>
    void TC<T>::func(int a, T2 b)
    {
        cout << "func()偏特化版本" << endl;
    }
    template <typename T>
    template <>     // //将会报错,有些资料上说目前的C+标准不允许在类模板之外全特化一个未被特化的类模板(指的是类模板A)的成员函数
    void func(int a, double b)
    {
        cout << "func()全特化版本" << endl;
    }
}
#endif

// detail2 的解决方法, 将类TC 进行全特化, 使用这个全特化的TC的成员函数, 特化这个成员函数是被允许的
namespace detail3
{
    template <typename T>
    class TC
    {
    public:
        template <typename T1, typename T2>
        void func(T1 a, T2 b);
        template <typename T2>
        void func(int a, T2 b);
        template <>
        void func(int a, double b);
        TC(int args, int args2) : args(args), args2(args2){};
        int args, args2 = 0;
    };
    template <typename T>
    template <typename T1, typename T2>
    void TC<T>::func(T1 a, T2 b)
    {
        cout << "func()泛化版本" << endl;
    }
    template <typename T>
    template <typename T2>
    void TC<T>::func(int a, T2 b)
    {
        cout << "func()偏特化版本" << endl;
    }
    // template <typename T>
    // template <>
    // void func(int a, double b)
    //{
    //     cout << "func()全特化版本" << endl;
    // }
    // 类模板的全特化版本
    template <>
    class TC<int>
    {
        template <typename T1, typename T2>
        void func(T1 a, T2 b);
        template <typename T2>
        void func(int a, T2 b);
        template <>
        void func(int a, double b);
        TC(int args, int args2) : args(args), args2(args2){};
        int args, args2 = 0;
        // 并不需要在特化的TC类中 写全特化成员函数func的声明
        /*
        template<>
        void func(int a,double b);

        */
    };

    template <>
    void TC<int>::func(int a, double b)
    {
        cout << "func()全特化版本" << endl;
    }
}
namespace detail4
{

    // 函数模板嵌套
    template <class _Ty1, class _Ty2>
    class MM
    {
    public:
        MM(_Ty1 one, _Ty2 two) : one(one), two(two) {}
        friend ostream &operator<<(ostream &out, const MM &mm)
        {
            out << mm.one << " " << mm.two;
            return out;
        }

    protected:
        _Ty1 one;
        _Ty2 two;
    };

    template <class _Ty> // 这个函数模板以另一个模板类型的数据为参数
    void print(_Ty data)
    {
        cout << data << endl; // 需要运算符重载
    }
    int main()
    {
        // 隐式调用
        print(MM<string, int>("小芳", 32)); // 在用类模板时显式写出来
        // 显示调用
        // 函数模板可以隐式调用 但是需要知道自己传的是什么类型 模板类型:MM<string, int>
        print<MM<string, int>>(MM<string, int>("小美", 28));
        // 起别名简化代码
        using MMType = MM<string, int>;
        // 显示调用优化
        print<MMType>(MMType("小美", 28));
        return 0;
    }

    /*输出*/

    小芳 32 小美 28
}
namespace detail
{
    // 类模板嵌套
    template <class _Ty1, class _Ty2>
    class MM
    {
    public:
        MM(_Ty1 one, _Ty2 two) : one(one), two(two) {}
        friend ostream &operator<<(ostream &out, const MM &mm)
        {
            out << mm.one << " " << mm.two;
            return out;
        }

    protected:
        _Ty1 one;
        _Ty2 two;
    };

    template <class _Ty1, class _Ty2> // 再写一个类去操作数据
    class Data
    {
    public:
        Data(_Ty1 one, _Ty2 two) : one(one), two(two) {}
        void print()
        {
            cout << one << " " << two << endl;
        }

    protected:
        _Ty1 one;
        _Ty2 two;
    };
    void testFunc()
    {
        // Data类的实例化   _Ty1 one: MM<string,int>   _Ty2 two: MM<double,double>
        Data<MM<string, int>, MM<double, double>>
            data(MM<string, int>("小芳", 18), MM<double, double>(89, 56));
        data.print(); // 传入两个对象 分别用 _Ty1 和 _Ty2 构造

        // 上面两行 等效下面四行代码 先把对象构建出来再传入对象即可
        MM<string, int> mmData("小芳", 18);
        MM<double, double> mmScore(89, 56);
        Data<MM<string, int>, MM<double, double>> mData(mmData, mmScore);
        mData.print();
    }
    int main()
    {
        testFunc();
    }

    /*输出*/

    小芳 18 89 56 小芳 18 89 56
}
int main(int argc, char **argv)
{
    // detail::TC<int> tc(1, 2);
    // tc.func(1, 2.0);
    // tc.func(2, "string");
    // tc.func("str", "ing");
}

类模板显式实例化定义/声明

类模版的隐式实例化

  • 模板(Templet)并不是真正的函数或类,它仅仅是编译器用来生成函数或类的一张“图纸”。模板不会占用内存,最终生成的函数或者类才会占用内存。由模板生成函数或类的过程叫做模板的实例化(Instantiate),相应地,针对某个类型生成的特定版本的函数或类叫做模板的一个实例(Instantiation)。

  • 模板的实例化是按需进行的,用到哪个类型就生成针对哪个类型的函数或类,不会提前生成过多的代码。也就是说,编译器会根据传递给类型参数的实参(也可以是编译器自己推演出来的实参)来生成一个特定版本的函数或类,并且相同的类型只生成一次。实例化的过程也很简单,就是将所有的类型参数用实参代替。

  • 另外需要注意的是类模板的实例化,通过类模板创建对象时并不会实例化所有的成员函数,只有等到真正调用它们时才会被实例化;如果一个成员函数永远不会被调用,那它就永远不会被实例化。这说明类的实例化是延迟的、局部的,编译器并不着急生成所有的代码。

  • 通过类模板创建对象时,一般只需要实例化成员变量和构造函数。成员变量被实例化后就能够知道对象的大小了(占用的字节数),构造函数被实例化后就能够知道如何初始化了;对象的创建过程就是分配一块大小已知的内存,并对这块内存进行初始化。

例子
在代码中实际使用模板类构造对象或者调用模板函数时,编译器会根据调用者传给模板的实参进行模板类型推导然后对模板进行实例化,此过程中的实例化即是隐式实例化。

template<typename T>
T add(T t1, T2)
{
    return t1 + t2;
}
 
template<typename T>
class Dylan
{
public:
    T m_data;
};
 
int main()
{
    int ret = add(3,4);//隐式实例化,int add<int>(int t1, int t2);
    Dylan<double> dylan;//隐式实例化
}

2. 显式实例化声明、定义

extern template int add<int>(int t1, int t2);//显式实例化声明
extern template class Dylan<int>;            //显式实例化声明
 
template int add<int>(int t1, int t2);       //显式实例化定义
template class Dylan<int>;                   //显式实例化定义
  • 当编译器遇到显式实例化声明时,表示实例化定义在程序的其他地方(相对于当前cpp文件)即在其他某一个cpp文件中定义,因此不再按照模板进行类型推导去生成隐式实例化定义。

  • 当编译器遇到显式实例化定义时,根据定义所提供的模板实参去实例化模板,生成针对该模板实参的实例化定义。

3. 显式实例化的用途

模板类、函数通常定义在头文件中,这些头文件会被很多cpp文件包含,在这些cpp文件中会多次使用这些模板,比如下面的例子:

//template.hpp
template<typename T>
class Dylan
{
public:
    T m_data;
};
 
//test1.cpp
#include "template.hpp"
Dylan<int> t1;
Dylan<int> t2;
 
//test2.cpp
#include "template.hpp"
Dylan<int> t3;
Dylan<int> t4;

在test1.cpp/test2.cpp 中多次实例化了Dylan类,按说编译完后的可执行程序中会包含多份Dylan的定义,然而实际上,整个程序中却只有一份Dylan的定义。这个处理是在编译和链接过程中实现的,目前主流的实现模式有两种:

a. Borland模式

Borland模式通过在编译器中加入与公共块等效的代码来解决模板实例化问题。在编译时,每个文件独立编译,遇到模板或者模板的实例化都不加选择地直接编译。在链接的时候将所有目标文件中的模板定义和实例化都收集起来,根据需要只保留一个。这种方法实现简单,但因为模板代码被重复编译,增加了编译时间。在这种模式下,我们编写代码应该尽量让模板的所有定义都放入头文件中,以确保模板能够被顺利地实例化。要支持此模式,编译器厂商必须更换支持此模式的链接器。

b. Cfront模式

AT&T编译器支持此模式,每个文件编译时,如果遇到模板定义和实例化都不直接编译,而是将其存储在模板存储库中(template repository)。模板存储库是一个自动维护的存储模板实例的地方。在链接时,链接器再根据实际需要编译出模板的实例化代码。这种方法效率高,但实现复杂。在这种模式下,我们应该尽量将非内联成员模板的定义分离到一个单独的文件中,进行单独编译。

在一个链接器支持Borland模式的编译目标(编译后的可执行文件)上,g++使用Borland模式解决实例化问题。比如ELF(Linux/GNU), Mac OS X, Microsoft windows, 否则,g++不支持上述两种模式。

如何避免Borland模式的缺点?

上面我们说g++实现的是Borland 模式,由于我们为每一份实例化生成代码,这样在大型程序中就有可能包含很多重复的实例化定义代码,虽然链接阶段,链接器会剔除这些重复的定义,但仍然会导致编译过程中的目标文件(或者共享库文件)过于庞大。这时候,我们就可以通过C++11的模板显式实例化的方法解决。看下面的代码:

// template.hpp
template<typename T>
class Dylan
{
public:
    Dylan(T t);
    
    T m_data;
};
 
// template.cpp
#include "template.hpp"
template<typename T>
Dylan<T>::Dylan(T t)
{
    m_data = t;
}

 
template class Dylan<int>; //模板实例化定义
 
// main.cpp
#include "template.hpp"
extern template class Dylan<int>; //模板实例化声明,告诉编译器,此实例化在其他文件中定义
                                  //不需要根据模板定义生成实例化代码  
                                 
int main()
{
    Dylan<int> dylan(3);//OK, 使用在template.cpp中的定义
    Dylan<float> dylan1(3.0);//error, 引发未定义错误
}
  • 上面的代码中,我们将模板类的具体定义放在template.cpp中,并且在template.cpp中通过显式实例化定义语句具体化了Dylan。在main.cpp中,我们通过显式实例化声明告诉编译器,Dylan将在其他文件中定义,不需要在本文件中根据template.hpp的类模板实例化Dylan。

  • 由于我们没有针对Dylan做显式实例化的声明和定义,因此Dylan
    dylan(3.0)会根据template.hpp中的类模板定义进行隐式实例化,然而构造函数是在template.cpp文件中定义的,在template.hpp中找不到构造函数的定义,因而报错。如果把构造函数的定义挪回template.hpp,那Dylan就能通过编译了。

  • Note:在编译中,如果指定-fno-implicit-templates,编译器就会禁止隐式实例化,从而只使用显式实例化。

注意

  • 模板显式实例化会将这种类型的类模板及所有成员函数都实例化出来,包括内联成员函数。
例子

hpp 文件(被多次调用的类模版)


#ifndef __test00HPP__
#define __test00HPP__

#include <iostream>
#include <vector>
using namespace std;
template <typename T1>
class Test00
{
public:
    template <typename T2=T1>
    Test00(T2 num1, T2 num2);

public:
    template <typename T3=T1>
    void mytest(T3 tmpt);
    T1 testNum;
};

template <typename T1>
template <typename T2>
Test00<T1>::Test00(T2 num1, T2 num2)
{
    cout << "num1=" << num1 << ",num2=" << num2 << endl;
}

template <typename T1>
template <typename T3>
void Test00<T1>::mytest(T3 tmpt)
{
    cout << "tmpt=" << tmpt << endl;
}
#endif

main.cpp (使用该hpp模板 文件的一个cpp文件,同时也是这个程序的主文件)

#include <iostream>
#include "test00.hpp"
#include "test01.h"

template Test00<float>;
//模板实例化定义
int main()
{
    Test00<float> fTest00(1, 2);
    fTest00.mytest(55);
    fTest00.testNum = 100.123;
    cout << fTest00.testNum << endl;

    myTest01();

    return 0;
}

test01.cpp(使用该hpp模板 文件的一个cpp文件)

#include "test01.h"

using namespace std;
extern template Test00<float>;
//模板实例化声明,告诉编译器,此实例化在其他文件中定义
                                  //不需要根据模板定义生成实例化代码  
void myTest01()
{
    Test00<float> fTest00(1, 2);
}

test01.h (分文件编写的方式,)

#ifndef __TEST01_H__
#define __TEST01_H__

#include "test00.hpp"

void myTest01();

#endif

注意,当我们进行显式实例化(template Test00;)后
hpp 文件中将会实例化出

    void mytest(float tmpt);
void Test00<float>::mytest(float tmpt)
{
    cout << "tmpt=" << tmpt << endl;
}


    Test00<float>::Test00(float num1, float  num2)
{
    cout << "num1=" << num1 << ",num2=" << num2 << endl;
}

模板的编译和链接问题

大多数人会按照如下方式组织非模板代码: 将类或者其他类型声明放在头文件(.hpp、.H、.h、.hh、.hxx)中。 将函数定义等放到一个单独的编译单元文件中(.cpp、.C、.c、.cc、.cxx)。

但是这种组织方式在包含模板的代码中却行不通,例如: 头文件:

// myfirst.hpp
#ifndef MYFIRST_HPP
#define MYFIRST_HPP
// declaration of template
template<typename T>
void printTypeof (T const&);
#endif // MYFIRST_HPP
定义函数模板的文件:

// myfirst.cpp
#include <iostream>
#include <typeinfo>
#include "myfirst.hpp"
// implementation/definition of template
template<typename T>
void printTypeof (T const& x) {
  std::cout << typeid(x).name() << '\n';
}
在另一个文件中使用该模板:

// myfirstmain.cpp
#include "myfirst.hpp"
// use of the template
int main() {
  double ice = 3.0;
  printTypeof(ice); // call function template for type double
}

在c/c++中,当编译阶段发现一个符号(printTypeof)没有定义只有声明时,编译器会假设它的定义在其他文件中,所以编译器会留一个”坑“给链接器linker,让它去填充真正的符号地址。

但是上面说过,模板是比较特殊的,需要在编译阶段进行instantiation,即需要进行模板参数类型推断,实例化模板,当然也就需要知道函数的定义。但是由于上面两个cpp文件都是单独的编译单元文件,所以当编译器编译myfirstmain.cpp时,它没有找到模板的定义,自然也就没有instantiation。

解决办法就是我们把模板的声明和定义都放在一个头文件。

变量模板

变量模板的定义,泛化,全特化,偏特化

// 一:定义,泛化,全特化,偏特化,使用方法
#include <iostream>
    using namespace std;

    // 变量模板的泛化版本
    template <typename T> //(1)
    T g_tmp{};            // 变量的零初始化方式

    // 变量模板的全特化,,这里的char可以和int不一样
    template <> //(2)
    char g_tmp<float>{'a'};

    // 变量模板的偏特化,从参数范围上的偏特化,必须要求是T*,依赖于T
    template <typename T> //(3)
    T g_tmp<T *>{10};

    // 变量模板的偏特化,从参数范围上的偏特化,必须要求是T*,依赖于T
    template <typename T> //(4)
    T g_tmp<const T *>{100};

    int main()
    {
        /*
        变量模板:Variable Templates,c++14引入,一般写在.h文件当中
        从感觉上,变量模板与函数模板有些类似,看起来象一个没有参数,但是有返回值的函数模板*/

        // 变量模板的,泛化,偏特化,以及全特化和使用
        g_tmp<int> = 20;
        std::cout << g_tmp<int> << std::endl;

        g_tmp<float>;
        std::cout << g_tmp<float> << std::endl;

        g_tmp<int *>;
        std::cout << g_tmp<int *> << std::endl;

        g_tmp<const int *>;
        std::cout << g_tmp<const int *> << std::endl;

        return 0;
    }

}


    int main()
    {
        TMP<int>::m_i<int> = 100;
        return 0;
    }

变量模板在C++14 之前的实现

方式一

// 变量模板在C++14 之前的实现
namespace detail4
{

    // https : // blog.csdn.net/lanchunhui/article/details/49835213
    //  C++14 前变量模板的实现
    //  第一种替代方案是,使用类模板的constexpr static数据成员的方式:
    template <typename T>
    struct PI
    {
        constexpr static T pi = T(3.1415926535897932385);
        // 这里必须使用关键字constexpr,而不可以是const
        // const 常量必须在编译器得到确定
        // 自C++11起,constexpr可以让表达式核定于编译期
    };

    // duplicate declaration
    template <typename T>
    constexpr T PI<T>::pi;

    int main(int, char **)
    {
        std::cout << PI<int>::pi << std::endl;
        // 3
        std::cout << PI<double>::pi << std::endl;
        // 3.14159
        return 0;
    }
    // 这种做法,因为constant是一种ODR(One Definition Rule)的定义规则。对constant的两次声明是有必要的,一次在类模板体内,一次在类模板体外,如上代码所示。
}

方式二

///变量模板在C++14 之前的实现
namespace detail5
// https: // blog.csdn.net/lanchunhui/article/details/49835213
// 另外一种解决方案是使用constexpr函数模板的形式,该函数返回期待的类型。
{
    template <typename T>
    T PI()
    {
        constexpr T pi = T(3.1415926535897932385);
        return pi;
    }

    int main(int, char **)
    {
        std::cout << PI<int>() << std::endl;
        std::cout << PI<double>() << std::endl;
        return 0;
    }
}

C++14 的变量模板

变量模板的作用

namespace detail6
{
    // 如果对模板或者C++标准感兴趣的开发者们相信都不会对变量模板感到陌生,我们今天就讲一讲变量模板

    // 从C++14 开始,变量也可以被某种类型参数化。称为变量模板。

    // 例如可以通过下面的代码定义pi,但是参数化了其类型:

    template <typename T = int> // 我们写作默认int
    T pi{};                     // 初始化列表 为0
    int main()
    {
        std::cout << precision(16);
        pi<int> = 20;                      // pi<>=20;效果一样
        std::cout << pi<int> << std::endl; // 20
        pi<double> = 3.14159265358;
        std::cout << pi<double> << std::endl; // 3.14159265358
        std::cout << pi<float> << std::endl;  // 0
        return 0;
    }
    // 注意,和其它几种模板类似,这个定义不要出现在函数内部或者块作用域内部。

    // 那么我们这样使用它,它是什么?我们没有创建局部变量,注意,它不是局部变量,这是一个全局变量,我们这个过程是实例化了一个全局变量。

    // 而且我们必须显性声明模板参数。

    // 我们讲讲它的一些用处

    // 打个比方,如果要使用一个类的静态数据,必须像下面这样调用

#include <iostream>
#include <string>
    template <typename T>
    class MyClass
    {
    public:
        static constexpr int max = 1000;
    };

    int main()
    {
        MyClass<int>::max;
    }
    // 得加上作用域解析运算符,但是,有了变量模板之后,我们可以

#include <iostream>
#include <string>
    template <typename T>
    class MyClass
    {
    public:
        static constexpr int max = 1000;
    };
    // 这意味着对于一个标准库的类:
    template <typename T>
    int myMax = MyClass<T>::max;
    namespace std
    {
        template <typename T>
        class numeric_limits
        {
        public:
            static constexpr bool is_signed = false;
        };
    }
    // 可以定义:
    template <typename T>
    constexpr bool isSigned = std::numeric_limits<T>::is_signed;
    int main()
    {
        // 应用工程师就可以使用下面这样的代码:
        auto i = myMax<std::string>;
        std::cout << i << std::endl;
        // 而不是:
        // auto i = MyClass<std::string>::max;
        // std::cout << i << std::endl;
        system("pause");
        return 0;
    }

}

C++17 类型特性后缀

namespace detail7
{

    // https : // blog.csdn.net/a4364634611/article/details/124663903

    //     类型特性后缀_v

    // C++17以后,标准库使用变量模板的技术为标准库中所有的类型特性定义便捷的用法,比如:

    // std::is_const_v<T>   // since C++17
    // 代替

    // std::is_const<T>::value  // since C++11
    // 标准库定义如下:

    // namespace std
    // {
    //     template<typename T> constexpr bool is_const_v = is_const<T>::value;
    // }

    // 熟悉标准库的开发者,对于 #include<type_traits> 不会陌生,我们举两个简单的例子

    template <typename T>
    auto
    func(T &a)
    {
        std::cout << typeid(a).name() << " ";
        if (std::is_array_v<T>)
        {
            std::cout << "是数组" << std::endl;
        }
        else
            std::cout << "不是数组" << std::endl;
    }

    template <typename T>
    auto func2(T &v)
    {
        std::cout << typeid(v).name() << " ";
        if (std::is_const_v<T>)
        {
            std::cout << "是const" << std::endl;
        }
        else
            std::cout << "不是const" << std::endl;
    }
    // 这第一种相当于使用了变量模板,也有不使用的写法

    template <typename T>
    auto func(T &a)
    {
        std::cout << typeid(a).name() << " ";
        if (std::is_array<T>::value)
        {
            std::cout << "是数组" << std::endl;
        }
        else
            std::cout << "不是数组" << std::endl;
    }

    template <typename T>
    auto func2(T &v)
    {
        std::cout << typeid(v).name() << " ";
        if (std::is_const<T>::value)
        {
            std::cout << "是const" << std::endl;
        }
        else
            std::cout << "不是const" << std::endl;
    }
    // 发现了吗?第二种相当于我们一开始介绍的使用作用域解析运算符来取出的值,标准委员会保留老式的写法,但是也更新形式的方法,大家理解即可。

    // 仅供参考,如有错误还请指正

}

变量模板的默认模板参数; 变量模板的非类型模板参数

namespace detail
{
    template <typename T>
    constexpr T pi{3.1415926535897932385};
}
namespace detail2
{
    // 变量模板可以有默认模板实参:
    template <typename T = long double>
    constexpr T pi = T{3.1415926535897932385};
}
namespace detail3
{
    // 变量模板可以由非类型参数进行参数化,这可以用于参数化初始值:
    template <int N>
    std::array<int, N> arr{}; // array with N elements, zero initialized

    template <auto N>
    constexpr decltype(N) dval = N; // type of dval depends on passed value
}

int main(int argc, char **argv)
{
    // 使用变量模板,必须指定它的类型:
    std::cout << detail::pi<double> << '\n';
    std::cout << detail::pi<float> << '\n';
    // 使用默认值或者其他类型
    std::cout << detail2::pi<> << '\n';      // outputs a long double
    std::cout << detail2::pi<float> << '\n'; // outputs a float

    // 但必须使用尖括号,仅仅使用pi是错误的:

    // std::cout << detail2::pi << '\n'; // ERROR

    // 变量模板可以由非类型参数进行参数化,这可以用于参数化初始值
    std::cout << detail3::dval<'c'> << '\n'; // N has value 'c' of type char

    detail3::arr<10>[0] = 42;                                 // sets first element of global arr
    for (std::size_t i = 0; i < detail3::arr<10>.size(); ++i) // uses values set in arr
    {
        std::cout << detail3::arr<10>[i] << '\n';
    }
}

别名模板

namespace detail8
{
    // 别名模板
    // https://blog.csdn.net/zwvista/article/details/54612025

    // 别名模板(alias template)
    // 别名模板:带模板参数的类型别名
    // 类型别名(type alias)
    // 是C++ 11新引入的语法形式: using newtype = oldtype;
    // 在语法功能上,它相当于传统C / C++ 语言中的typedef语句: typedef oldtype newtype;
    // 可以看出,类型别名中原有类型和别名的定义顺序与typedef语句正好相反。除此之外,类型别名与typedef语句还有一点不同,类型别名可以加上模板参数形成别名模板 : template <typename...>
    //                                                                                                                                                        using newtype = oldtype<...>;
    // 注:C++ 11引入类型别名意图取代typedef语句的原因在于:无法直接在typedef语句的基础上直接构建别名模板。这是因为typedef语句自身存在某些局限性,直接给typedef加上模板参数会带来语法解析上的问题。  
    template <typename T, typename U>
    struct A;

    template <typename T>
    struct B
    {
        typedef A<T, int> type;
    };

    template <typename T>
    using C = A<T, int>;

    template <typename T>
    using D = typename B<T>::type;
    // 代码说明:
    // 假设我们有一个带两个模板参数T和U的类模板A。现在我们需要声明一个只带一个模板参数T的类模板,使其等价于模板参数U为int类型的A模板。也就是说,我们需要一个模板参数T任意,模板参数U为int类型的A模板的别名,或者说A<T, int>的别名。
    // 在C++11之前,答案为类模板B。要定义类型别名,必然要使用typedef。但由于typedef不能带模板参数,所以typedef必须被嵌入到一个带模板参数的类模板里面。在模板参数为T的类模板B里面,类型type被定义成A<T, int>的别名。也就是说typename B<T>::type被定义成了A<T, int>的别名。
    // 在C++11之后,答案为别名模板C。类型别名直接就可以带模板参数。C<T>直接被定义成了A<T, int>的别名。
    // 如果出于某种原因,在定义别名的时候无法使用类模板A而只能使用类模板B,别名模板也能发挥作用。这里D<T>直接被定义成了typename B<T>::type的别名。由于后者是A<T, int>的别名,所以D<T>其实也是A<T, int>的别名。
    // 这段代码展示了别名模板的主要用途:1.为部分模板参数固定的类模板提供别名。2.为类模板中嵌入的类型定义提供别名。
}
namespace detail9
{
    // 一:定义,泛化,全特化,偏特化,使用方法
#include <iostream>
    using namespace std;

    // 变量模板的泛化版本
    template <typename T> //(1)
    T g_tmp{};            // 变量的零初始化方式

    // 变量模板的全特化,,这里的char可以和int不一样
    template <> //(2)
    char g_tmp<float>{'a'};

    // 变量模板的偏特化,从参数范围上的偏特化,必须要求是T*,依赖于T
    template <typename T> //(3)
    T g_tmp<T *>{10};

    // 变量模板的偏特化,从参数范围上的偏特化,必须要求是T*,依赖于T
    template <typename T> //(4)
    T g_tmp<const T *>{100};

    int main()
    {
        /*
        变量模板:Variable Templates,c++14引入,一般写在.h文件当中
        从感觉上,变量模板与函数模板有些类似,看起来象一个没有参数,但是有返回值的函数模板*/

        // 变量模板的,泛化,偏特化,以及全特化和使用
        g_tmp<int> = 20;
        std::cout << g_tmp<int> << std::endl;

        g_tmp<float>;
        std::cout << g_tmp<float> << std::endl;

        g_tmp<int *>;
        std::cout << g_tmp<int *> << std::endl;

        g_tmp<const int *>;
        std::cout << g_tmp<const int *> << std::endl;

        return 0;
    }

}
//typedef给固定类型起别名
typedef std::map<std::string, int> map_s_i;
map_s_i mymap;
mymap.insert({"first", 1});

typedef std::map<std::string, std::string> map_s_s;
map_s_s mymap2;
mymap2.insert({"first", "second"});

//c++98通过类模板实现类型名不固定
template<typename T>
struct map_s {
	typedef std::map<std::string, T> type;
};
map_s<int>::type map;
map.insert({"first", 1});

//c++11
template<typename T>
using map_s = std::map<std::string, T>;
map_s<int> map;
map.insert({"first", 1});

//using用来给类型有关模板起别名
//using包含了typedef的所有功能
typedef unsigned int uint_t;
using uint_t = unsigned int;

typedef std::map<std::string, int> map_s_i;
using map_s_i = std::map<std::string, int>;

typedef int(*FunType)(int,int);
using FunType = int(*)(int,int);


int RealFunc(int i, int j) {return 3;}
template<typename T>
using myfunc_M = int(*)(T,T);
myfunc_M<int> pointFunc;//类型名,非类模板实例化后的类
pointFunc = RealFunc;
cout <<pointFunc(1, 6) <<endl;

成员变量模板

namespace detail10
// 成员变量模板
{
#include <iostream>
    // #include <boost/type_index.hpp>
    using namespace std;

    template <typename T>
    class TMP
    {
    public:
        template <typename U>
        // U m_i = {}; 只可以使用静态成员变量
        static U m_i;
    };

    template <typename T>
    template <typename U>
    U TMP<T>::m_i = 10;

    int main()
    {
        TMP<int>::m_i<int> = 100;
        return 0;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

丁金金

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

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

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

打赏作者

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

抵扣说明:

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

余额充值