zoukankan      html  css  js  c++  java
  • c++11-17 模板核心知识(五)—— 理解模板参数推导规则

    首先我们定义一下本文通用的模板定义与调用:

    template<typename T>
    void f(ParamType param);
    
    ......
    f(expr); // call f with some expression
    

    在编译阶段使用expr来推断ParamTypeT这两个类型。这两个类型通常不同,因为ParamType会有const和引用等修饰。例如:

    template<typename T>
    void f(const T& param);      // ParamType is const T&
    
    int x = 0;
    f(x);      // call f with an int
    

    这里,T被推断成int,但是ParamType的类型是const T&

    直觉下T的类型应该和expr的一样,比如上面的例子中,exprT的类型都是int。但是会有一些例外情况:T的类型不仅依赖expr,还依赖ParamType。总共分为三大类:

    • ParamType是一个指针或者引用,但不是universal reference(或者叫forwarding references).
    • ParamType是一个universal reference
    • ParamType既不是指针也不是引用。

    Case 1 : ParamType是一个指针或者引用,但不是universal reference

    • 如果expr是一个引用,忽略其引用部分。
    • 比较exprParamType的类型来决定T的类型。

    T&

    template<typename T>
    void f(T& param);       // param is a reference
    
    ......
    int x = 27;                  // x is an int
    const int cx = x;       // cx is a const int
    const int& rx = x;     // rx is a reference to x as a const int
    
    // call f
    f(x);            // T is int, param's type is int&
    f(cx);          // T is const int,  param's type is const int&
    f(rx);         // T is const int,  param's type is const int&
    

    上面例子是左值引用,但是这点对右值引用也适用。
    注意第三点,const修饰符依旧保留。 这和普通函数的类似调用有区别:

    void f(int &x){
    
    }
    
    ... 
    const int x  = 10;
    f(x);       // error
    

    const T&

    如果给ParamType加上const,情况也没有太大变化:

    template<typename T>
    void f(const T& param);        // param is now a ref-to-const
    
    ......
    int x = 27;                // as before
    const int cx = x;     // as before
    const int& rx = x;    // as before
    
    ......
    f(x);         // T is int, param's type is const int&
    f(cx);     // T is int, param's type is const int&
    f(rx);      // T is int, param's type is const int&
    

    T*

    改为指针也一样:

    template<typename T>
    void f(T* param); // param is now a pointer
    
    ......
    int x = 27;                    
    const int *px = &x;      
    
    f(&x);               // T is int, param's type is int*
    f(px);              // T is const int, param's type is const int*
    

    Case 2 : ParamType是Universal Reference

    • 如果expr是左值,那么TParamType会被推断为左值引用。
    • 如果expr是右值,那么就是Case 1的情况。
    template<typename T>
    void f(T&& param);       // param is now a universal reference
    
    ......
    int x = 27;                
    const int cx = x;    
    const int& rx = x;
    

    调用:

    f(x);          // x is lvalue, so T is int&, param's type is also int&
    f(cx);         // cx is lvalue, so T is const int&, param's type is also const int&
    f(rx);        // rx is lvalue, so T is const int&, param's type is also const int&
    f(27);        // 27 is rvalue, so T is int, param's type is therefore int&&
    

    如果之前了解过完美转发和折叠引用的概念,结合Case1,这一个规则还是比较好理解的。

    注意区别Universal Reference与右值引用

    这两点需要区分清楚,比如:

    template<typename T>
    void f(T&& param);           // universal reference
    
    
    template<typename T>
    void f(std::vector<T>&& param);       // rvalue reference
    

    有一个通用规则 : universal reference会有类型推断的过程。具体在后面的单独文章会讲,跟这篇文章的主题关系不大,这里稍微提一下 : )

    Case 3 : ParamType既不是指针也不是引用

    这种情况就是pass-by-value的情况:

    template<typename T>
    void f(T param); // param is now passed by value
    

    这意味着,param是一个被拷贝的全新对象,也就是param决定着T的类型:

    • 如果expr是引用类型,忽略。
    • 如果expr带有const、volatile,忽略。
    int x = 27;
    const int cx = x; 
    const int& rx = x; 
    f(x);         // T's and param's types are both int
    f(cx);      // T's and param's types are again both int
    f(rx);      // T's and param's types are still both int
    

    忽略const和volatile也比较好理解:参数是值拷贝,所以形参和实参其实是互相独立的。正如下面代码可以将const int传递给int,但是声明为引用则不行:

    void f(int x){
    
    }
    
    int main() {
      const int x  = 10;
    
      f(x);       
    }
    

    注意忽略的const是针对参数本身的,而不针对指针指向的const对象:

    template<typename T>
    void f(T param);
    
    ......
    const char* const ptr = "Fun with pointers";       // ptr is const pointer to const object
    f(ptr);             // pass arg of type const char * const
    

    这个按照值传递的是ptr,所以ptr的const会被忽略,但是ptr指向的对象依然是const。

    数组作为参数

    数组类型和指针类型是两种类型,但是有时候他们是可以互换的,比如在下面这种情况下,数组会decay成指针:

    const char name[] = "J. P. Briggs";     // name's type is const char[13]
    const char * ptrToName = name;       // array decays to pointer
    

    在普通函数中,函数形参为数组类型和指针类型是等价的:

    void myFunc(int param[]);
    void myFunc1(int* param);         // same function as above
    

    但是数组作为模板参数是比较特殊的一种情况。

    ParamType按值传递

    template<typename T>
    void f(T param); // template with by-value parameter
    
    ......
    const char name[] = "J. P. Briggs";     // name's type is  const char[13]
    
    f(name);           // name is array, but T deduced as const char*
    

    这种情况下,T被推断为指针类型const char*.

    ParamType为引用类型

    template<typename T>
    void f(T& param); 
    
    ......
    const char name[] = "J. P. Briggs";     // name's type is  const char[13]
    f(name);             // pass array to f
    

    现在T被推断为数组类型const char [13]ParamTypeconst char (&)[13],这种情况是很特殊的,要与ParamType按值传递区分开。

    我们可以利用上面这种特性定义一个模板来推断数组的大小,这种用法还蛮常见的:

    template<typename T, std::size_t N> 
    constexpr std::size_t arraySize(T (&)[N]) noexcept  {
        return N;
    }
    
    ......
    int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 };
    std::array<int, arraySize(keyVals)> mappedVals;
    

    image

    函数作为参数

    上面讨论的关于数组的情况同样适用于函数作为参数,函数类型同样也可以decay成函数指针:

    void someFunc(int, double);        // someFunc is a function;type is void(int, double)
    template <typename T> void f1(T param);     // in f1, param passed by value
    template <typename T> void f2(T &param);    // in f2, param passed by ref
    f1(someFunc);        // param deduced as ptr-to-func; type is void (*)(int, double)
    f2(someFunc);      // param deduced as ref-to-func; type is void (&)(int, double)
    

    不过这在平时应用中也没有太大差别。

    (完)

    朋友们可以关注下我的公众号,获得最及时的更新:

  • 相关阅读:
    leetcode 113. Path Sum II (路径和) 解题思路和方法
    [置顶] T-sql sql server 设置主键约束、标示列、唯一约束、默认值、约束、创建表
    python多线程threading
    可视化MNIST之降维探索Visualizing MNIST: An Exploration of Dimensionality Reduction
    神经网络:卷积神经网络CNN
    卷积神经网络(CNN)
    数据挖掘(10):卷积神经网络算法的一个实现
    CNN卷积神经网络在自然语言处理的应用
    卷积神经网络CNN全面解析
    Matlab多个Figure图合成一个Fig
  • 原文地址:https://www.cnblogs.com/zhangyachen/p/14013605.html
Copyright © 2011-2022 走看看