美文网首页
Effective Modern C++ - 1: 类型推断

Effective Modern C++ - 1: 类型推断

作者: my_passion | 来源:发表于2022-10-22 20:15 被阅读0次

    part1 类型推断

    C++98
        只有1种: 函数模板 实参推断
        
    C++11 
        auto 
        decltype
        
    好处 
        不用拼写 明显或多余的类型
        code 自适应: code 某一点上更改类型, 自动通过类型推导传播到其他位置
        
    弊端 
        有时代码更难理解 
        
    大多情况下, auto出现在decltype表达式中
    
    C++14用decltype(auto) 构造 
    

    item1 模板类型推断: 即 函数模板实参推断

    规则非常自然

    remember: 模板类型推导中

    (1) 引用 传递的实参: 忽略 实参的引用性

    (2) 万能引用 传递的实参: 左值实参 得到 特殊待遇(T 和 ParamType 都被推断为 左值引用)

    (3) pass-by-value 实参: 依次忽略 实参(expr)本身的 reference -> const -> volatile 特性

    (4) 数组/函数名 实参: 衰变为指针, 用于 初始化 数组/函数 引用 T (&rArr)[N])/ReturnType (&rFunc)(argType) 时才有意义

    Note

    (1) 函数模板的形参类型 ParamType 只应该为 3类4种形式:

    [1] 值传递 T param: 不应该加 const/volatile, 实参的 const/volatile 特性会被去掉

    [2/3] 引用传递 T& param 或 const T& param

    [4] 万能引用传递 T&& param: 不应该加 const, 否则就不是万能引用了

    (2) 引用传递 + ParamType 不显式含 const 的模板(T& parameter): 编译器不允许 实参(arg) 为 右值

    引用传递 + ParamType 显式含 const 的模板(const T& parameter): 编译器允许 实参(arg) 为 右值

    #include <iostream>
    
    template<typename T>
    void f(T& param) // param is a reference
    {
        std::cout << param << std::endl;
    }
    
    int main()
    {
        f(2); // VS2019下, compile error: 无法将参数 1 从“int”转换为“T &”
    }
    
    #include <iostream>
    
    template<typename T>
    void f(const T& param) // param is a reference
    {
        std::cout << param << std::endl;
    }
    
    int main()
    {
        f(2); // VS2019下, comiple/run ok
    }
    

    分析

        template<typename T>
        void f(ParamType param);
    
        f(expr); // call f with some expression
                 // 从 expr 推导出  T 和 ParamType
    

    T 的类型推导 依赖于2点: [1] expr 的类型 [2] ParamType 的形式

    3 种 cases 
    

    ParamType 是

    1 指针或引用 类型, 但 not 万能(universal)引用

    工作原理

    (1) 若 expr 的类型是引用, 则 忽略 expr 的引用部分

    (2) 模式匹配 expr 的类型ParamType, 以确定 T, 再由 T 推断出 ParamType

    [1] 引用传递 + ParamType 不显式含 const 的模板: T& parameter

    需要将 const作为 T 的一部分 来推导

    template<typename T>
    void f(T& param); // param is a reference
    
    int x = 27;         // 1] x is an int
    const int cx = x;   // 2] cx is a const int
    const int& rx = x;  // 3] rx is a const int& -> 忽略引用部分: const int 
    
    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&
    

    2] => 常量对象 引用传递(由 ParamType 决定)给模板安全的, 无论 ParamType 是否显式含 const

    原因: 实参(arg)的 const 特性会被推断出来, 作为 T 的一部分

    [2] 引用传递 + ParamType 显式含 const 的模板: const T& param

    不再需要将 const作为 T 的一部分 来推导 => 实参(arg)的 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&
    

    [3/4] param 是 pointer 或 pointer to const, 推理完全同上

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

    2 万能引用

    详见 item24
    

    类型推断 区分 实参(expr)左值还是右值

    工作原理:

    (1) expr 是左值, 则 T 和 ParamType 都被推断为 左值引用 -> 双重不寻常

    [1] 这是模板类型推导中的 唯一情况, 其中 T 被推断为 引用

    [2] 尽管 ParamType 声明使用右值引用的语法, 其推导类型是 左值引用

    (2) expr 是右值,则 "正常"(Case1)规则适用

    template<typename T>
    void f(T&& param);  // param is now a universal reference
    
    int x = 27;         // as before
    const int cx = x;   // as before
    const int& rx = x;  // as before
    
    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&&
    

    3 既非 指针, 也非 引用

    工作原理

    依次忽略 实参(expr)本身的 reference -> const -> volatile 特性, 但保持 实参(若为 指针/引用)所指对象的 const 特性

    原因: 值传递 -> copy: 副本没有 reference/const/volatile 特性

    3.1 指针/引用 实参

    template<typename T>
    void f(T param); // param is now passed by value
    
    int x = 27;         // as before
    const int cx = x;   // as before
    const int& rx = x;  // as before
    
    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
    
    template<typename T>
    void f(T param); // param is still passed by value
                     // T is const char* => param is const char*
    
    const char* const ptr =     // ptr is const pointer to const object: const char* const
        "Fun with pointers";
     
    f(ptr); // pass arg of type const char * const
    

    3.2 数组实参: 对类型推断而言, 与指针类型不同

    指向常量的指针 const char*常量数组 const char[13] 不同: 所指的1个元素/数组所有元素 const

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

    (1) 常量数组(const char[])值传递给模板: T/ParamType 被推断为 指向常量的指针 const char*/const char*

    template<typename T>
    void f(T param); // template with by-value parameter
    
    f(name); // name is array, but T deduced as const char*
    

    (2) 常量数组(const char[])引用传递给模板: T/ParamType 被推断为 数组的实际类型(char[N=...])/数组的引用类型(const char(&)[N])

    template<typename T>
    void f(T& param); // template with by-reference parameter
    
    f(name); // name is 数组的引用 const char(&)[13], T deduced as 数组的实际类型 char[13]) 
    

    声明数组的引用的模板, 支持 推导 数组包含的元素数 + constexpr -> 结果可用在编译期

    // return size of an array as a 编译期常量
    // 数组参数无名, 因为我们只关心数组包含的元素个数
    template<typename T, std::size_t N> 
        constexpr std::size_t 
    arraySize(T (&)[N]) noexcept    // below on
    {                               // constexpr
        return N;                   // and
    }                               // noexcept
    
    int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; // keyVals has 7 elements
     
    int mappedVals[ arraySize(keyVals) ]; // so does mappedVals
    

    更 modern的做法: std::array

    std::array<int, arraySize(keyVals)> mappedVals; // mappedVals' size is 7
    

    3.3 函数实参

    衰变为指针的2种东西: 数组 / 函数 -> 指针/函数指针

    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)
    

    这在实践中没有意义

    item2 auto

    remember

    (1) 通常, auto 与 模板类型推断 本质相同, 但 auto 类型推断 假定 带大括号的 初始化 表示 初始化列表(std::initializer_list)

    (2) 函数返回类型lambda参数中的 auto 表示 模板类型推断, 而非 auto 类型推断

    1 通常: 编译器推断规则同 模板实参推断(expr -> T + ParamType)

    auto 扮演的角色 : T

    type specifier(类型限定符) : ParamType 中除 T部分之外的 限定符

    auto x = 27;
    
    const auto cx = x;
    
    const auto& rx = x;
    
    template<typename T>        // conceptual template for
    void func_for_x(T param);   // deducing x's type
    
    func_for_x(27); // conceptual call: param's
                    // deduced type is x's type
                    
    template<typename T>             // conceptual template for
    void func_for_cx(const T param); // deducing cx's type
    
    func_for_cx(x); // conceptual call: param's
                    // deduced type is cx's type
     
    template<typename T>              // conceptual template for
    void func_for_rx(const T& param); // deducing rx's type
    
    func_for_rx(x); // conceptual call: param's
                    // deduced type is rx's type
    

    Case3/1

    auto x = 27;        // case 3 (x is neither ptr nor reference)
    const auto cx = x;  // case 3 (cx isn't either)
    
    const auto& rx = x; // case 1 (rx is a non-universal ref.)
    

    Case2

    auto&& uref1 = x;   // x is int and lvalue,
                        // so uref1's type is int&
                        
    auto&& uref2 = cx;  // cx is const int and lvalue,
                        // so uref2's type is const int&
                        
    auto&& uref3 = 27;  // 27 is int and rvalue,
                        // so uref3's type is int&&
    

    数组/函数

    const char name[] = // name's type is const char[13]
        "R. N. Briggs";
        
    auto arr1 = name;   // arr1's type is const char*
    
    auto& arr2 = name;  // arr2's type is
                        // const char (&)[13]
     
    void someFunc(int, double); // someFunc is a function;
                                // type is void(int, double)
                                
    auto func1 = someFunc;      // func1's type is
                                // void (*)(int, double)
                                
    auto& func2 = someFunc;     // func2's type is
                                // void (&)(int, double)
    

    2 auto 和 模板类型推断 唯一真正的区别: auto 假设 带大括号({})的初始值初始化列表 (std::initializer_list), 模板类型推断 则不

    (1) auto 用于初始化

    [1] 初始初始化单个元素

    auto x1 = 27;       // type is int, value is 27
    auto x2(27);        // ditto
    

    [2] 初始化列表 std::initializer_list<int>

    auto x3 = { 27 };   // type is std::initializer_list<int>
                        // value is { 27 }
    auto x4{ 27 };      // ditto
    

    (2) 模板类型推断: 模板函数的形参 不含/含 初始化列表, 不可/可推断出 初始化列表类型

    auto x = { 11, 23, 9 }; // x's type is
                            // std::initializer_list<int>
                            
    template<typename T>    // template with parameter
    void f(T param);        // declaration equivalent to
                            // x's declaration
                        
    f({ 11, 23, 9 });       // error! can't deduce type for T
    
    template<typename T>
    void f(std::initializer_list<T> initList);
    
    f({ 11, 23, 9 });   // T deduced as int, and initList's
                        // type is std::initializer_list<int>
    

    3 auto 作 returnType/ lambda的paraType: 意味着模板类型推断 => 此时, auto 无法实现 初始化列表的推断

    auto createInitList()
    {
        return { 1, 2, 3 }; // error: can't deduce type
    }                    // for { 1, 2, 3 }
    
    std::vector<int> v;
    …
    
    auto resetV =
        [&v](const auto& newValue) { v = newValue; }; // C++14
    …
        
    resetV({ 1, 2, 3 }); // error! can't deduce type
                         // for { 1, 2, 3 }
    

    item3 decltype

    推断 名称表达式 的 类型

    1 decltype VS. auto

    auto 用于 类型推断时, 修饰的是变量, 且 变量必须初始化; 无需/不能定义变量时, 用 decltype: 编译时推断 表达式的类型 decltype(expr)

    2 decltype

    C++11中, decltype 主要用途是 声明 函数模板, 其中returnType 取决于 para

    函数名之前用 auto 与 类型推断无关, 表示用 尾部返回类型, 即 返回类型 在 -> 之后 声明, 此时, 类型推断由 decltype 实现

    (1) 仅支持左值容器: 操作 + 将 operator[] 包装1层返回时, 去掉引用特性

    template<typename Container, typename Index> // works, but
    auto authAndAccess(Container& c, Index i)    // requires
        -> decltype(c[i])                        // refinement
    {
        authenticateUser();
        return c[i];
    }
    

    (2) Note: 下标操作符 operator[] 返回 引用, 但 尾部返回类型 相应的类型推断会去掉引用特性

    => 函数返回值 不再是引用 => 不能被赋值

    std::deque<int> d;
    
    // …
    
    authAndAccess(d, 5) = 10;   // authenticate user, return d[5]: 是 int&, 但函数返回类型是 int 
                                // then assign 10 to it;
                                // this won't compile!
    

    (3) 支持左/右值容器: 工厂函数返回容器(临时容器对象) -> operator[] 取其第 n 个元素 -> copy 返回: pass by value

    template<typename Container, typename Index> // final
    auto // C++11
    authAndAccess(Container&& c, Index i) // version
        -> decltype( std::forward<Container>(c)[i] )
    {
        authenticateUser();
        return std::forward<Container>(c)[i];
    }
    
    std::deque<std::string> makeStringDeque(); // factory function
    
    // copy 5th element of deque returned from makeStringDeque
    auto s = authAndAccess(makeStringDeque(), 5);
    

    相关文章

      网友评论

          本文标题:Effective Modern C++ - 1: 类型推断

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