美文网首页
第一章:函数模板

第一章:函数模板

作者: 找不到工作 | 来源:发表于2020-12-20 10:43 被阅读0次

    函数模板是指被参数化的模板,可以代表一类函数。

    1.1 初识函数模板

    函数模板和普通函数看起来很相似,但是部分元素是未确定的。

    1.1.1 定义模板

    下面定义了从两个值中返回最大值的函数。

    template<typename T>
    T max(T a, T b)
    {
        // if b < a then yield a else yield b 
        return b < a ? a : b;
    }
    

    我们使用

    template<typename T>
    

    声明了模板化的参数是T

    由于历史原因,你可以用 classtypename 定义类型参数,但是 struct 不行。推荐使用 typename 避免歧义。

    1.1.2 使用模板

    通过以下程序来使用模板:

    #include "max1.hpp"
    #include <iostream>
    #include <string>
    
    int main()
    {
        int i = 42;
        std::cout << "max(7,i):      " << ::max(7,i) << '\n';
    
        double f1 = 3.4; double f2 = -6.7;
        std::cout << "max(f1,f2):    " << ::max(f1,f2) << '\n';
    
        std::string s1 = "mathematics"; std::string s2 = "math";
        std::cout << "max(s1,s2):    " << ::max(s1,s2) << '\n';
    }
    

    我们可以看到分别对 int, float, std::string 三个类型使用了模板。输出为:

    max(7,i):      42
    max(f1,f2):    3.4
    max(s1,s2):    mathematics
    

    此外,我们通过使用 ::max 指明了 max 是在全局 namespace 的max,而非 std::max

    模板并非被编译成一种能够处理所有类型的函数,而是对每个使用模板的类型都生成一个函数。因此,对于 int 的调用,在编译时将 T 替换为 int,编译了如下函数:

    int max (int a, int b)
    {
        return b < a ? a : b;
    }
    

    类似的,其他两个调用相当于用 floatstd::string 替换了 T

    这个过程称为模板的“实例化”。

    1.1.3 两步翻译

    如果实例化一个模板不支持的类型,会引起编译错误。模板是通过两个步骤编译的:

    1. “定义”阶段,无视模板参数 T,检查模板语法
    • 发现语法错误,例如缺少;,使用未定义的变量等
    • 检查不依赖 Tstatic_assert
    1. “实例化”阶段,再次检查,包括依赖 T 的部分

    例如:

    template<typename T>
    void foo(T t)
    {
       undeclared();        // first-phase compile-time error if undeclared() unknown
       undeclared(t);        // second-phase compile-time error if undeclared(T) unknown
       static_assert(sizeof(int) > 10,          // always fails if sizeof(int)<=10
                     "int too small");
       static_assert(sizeof(T) > 10,            //fails if instantiated for T with size <=10
                     "T too small");
    }
    

    编译和链接

    两步翻译引出一个处理模板时的难题:当使用的函数模板需要实例化时,编译器需要看到模板的实现。

    对于普通的函数,编译和链接是分开的。只需要声明函数就足够编译了,而函数模板则需要看到实现。我们会在第九章讨论如何解决这个问题,为了简便,我们教程中都会将模板的实现放在头文件里

    1.2 模板类型推断

    当我们调用函数模板时,T 是由我们传入的参数类型决定的。

    类型推断中的类型转换

    自动类型转换在类型推断中的限制:

    • 如果所有的参数都是传引用(&),则不会进行类型转换。

    • 如果所有的参数都是传值,则只允许“简单”转换:constvolatile 会被忽略,引用会被转为被引用类型,数组和函数会被转为指针。

    例如:

    template<typename T>
    T max (T a, T b);
    …
    int const c = 42;
    max(i, c);            // OK: T is deduced as int
    max(c, c);            // OK: T is deduced as int
    int& ir = i;
    max(i, ir);          // OK: T is deduced as int
    int arr[4];
    foo(&i, arr);        // OK: T is deduced as int*
    

    然而,如果需要类型转换,例如 intfloat,就失败。例如:

    max(4, 7.2);        // ERROR: T can be deduced as int or double
    std::string s;
    foo("hello", s);    //ERROR: T can be deduced as char const[6] or std::string
    

    我们需要提前处理好类型再调用。

    1.3 多个模板参数

    如果我们确实需要比较不同类型的参数,那我们可能需要写如下代码

    template<typename T1, typename T2>
    T1 max (T1 a, T2 b)
    {
        return b < a ? a : b; }
    …
    auto m = ::max(4, 7.2);        // OK, but type of first argument defines 
    

    但是这个返回值显然很奇怪,以上的例子会返回 7(int) 而非 7.2(float)。

    从 C++14 开始,我们可以让编译器推断返回类型,于是可以更优美地实现:

    template<typename T1, typename T2>
    auto max (T1 a, T2 b)
    {
      return b < a ? a : b;
    }
    

    有时我们希望有同样类型的返回值,这时可以使用 std::common_type<>::type 返回多个类型中最 general 的类型。例如:

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

    (吐槽:所以为什么不一开始就转成一个类型,搞这么复杂)

    1.4 默认模板参数

    不常用,略过

    1.5 模板函数重载

    就像普通函数,模板函数也能被 overload。

    // maximum of two int values:
    int max (int a, int b)
    {
      return b < a ? a : b;
    }
    
    // maximum of two values of any type:
    template<typename T>
    T max (T a, T b)
    {
      return b < a ? a : b;
    }
    
    int main()
    {
      ::max(7, 42);                // calls the nontemplate for two ints 
      ::max(7.0, 42.0);            // calls max<double> (by argument deduction)
      ::max(’a’, ’b’);              //calls max<char> (by argument deduction) 
      ::max<>(7, 42);              // calls max<int> (by argument deduction) 
      ::max<double>(7, 42);        // calls max<double> (no argument deduction)
      ::max(’a’, 42.7);             //calls the nontemplate for two ints
    }
    

    可以看到,规则有:

    • 采用在不转化类型前提下最匹配的函数
    • 同等匹配度条件下,优先非模板函数,但可以用 <> 指明使用模板生成的函数

    我们可以利用这个原则,优化对于指针类型或者const char* 类型的比较。

    #include   <cstring>
    #include   <string>
    
    // maximum of two values of any type:
    template<typename    T>
    T max (T a, T b)
    {
       return      b < a ? a : b;
    }
    
    // maximum of two pointers:
    template<typename    T>
    T* max (T* a, T* b)
    {
       return       *b < *a   ? a : b;
    }
    
    // maximum of two C-strings:
    char const* max (char   const* a,   char const* b)
    {
       return       std::strcmp(b,a) < 0   ? a : b;
    }
    
    int   main ()
    {
       int a = 7;
       int b = 42;
       auto m1 = ::max(a,b);         // max() for two values of type int
    
       std::string s1 =   "hey"; "
       std::string s2 =   "you"; "
       auto m2 = ::max(s1,s2);       // max() for two values of type std::string
    
       int* p1 = &b;
       int* p2 = &a;
       auto m3 = ::max(p1,p2);       // max() for two pointers
    
       char const* x =  hello"; "
       char const* y =  "world"; "
       auto m4 = ::max(x,y);         // max() for two C-strings
     }
    

    相关文章

      网友评论

          本文标题:第一章:函数模板

          本文链接:https://www.haomeiwen.com/subject/ctycnktx.html