读书笔记 《Effective modern C++》之 Deducing Types & auto(一)

1.Deducing Types

Item 1: 理解模板的类别推导

template<typename T>
void f(ParamType param);

f(expr); // deduce T and ParamType from expr

Case 1: ParamType是引用或者指针,但不是万能引用

  • If expr’s type is a reference, ignore the reference part. 忽略reference-ness(const int& rx = x;)
  • Then pattern-match expr’s type against ParamType to determine T. T与ParamType一同推导
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
                   // reference-ness会被忽略

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&
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&

ParamType 为指针的时候

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*

Case 2: ParamType是万能引用

为了区分左右值,万能引用使用了这种推断方法。
左值 -> T是左值引用, ParamType& &&
右值 -> T是正常类型, ParamType &&

  • If expr is an lvalue, both T and ParamType are deduced to be lvalue references. That’s doubly unusual. First, it’s the only situation in template type deduction where T is deduced to be a reference. Second, although ParamType is declared using the syntax for an rvalue reference, its deduced type is an lvalue reference.
  • If expr is an rvalue, the “normal” (i.e., Case 1) rules apply.
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&&

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

template<typename T>
void f(T param); // param is now passed by value
  • As before, if expr’s type is a reference, ignore the reference part. 同样忽略reference-ness
  • If, after ignoring expr’s reference-ness, expr is const, ignore that, too. If it’s volatile, also ignore that. (volatile objects are uncommon. They’re generally used only for implementing device drivers. For details, see Item 40.) 忽略const和volatile,因为是值传递,所以param是一个复制,不用const也有道理。
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

const char* const ptr = // ptr is const pointer to const object
"Fun with pointers";

f(ptr); // pass arg of type const char * const, param -> const char*
        // 指针不是const,指向的物体还是const

传入数组, 退化成指针

const char name[] = "J. P. Briggs"; // name's type is const char[13]

const char * ptrToName = name; // array decays to pointer

f(name);                       // name is array, but T deduced as const char*, array declaration is treated as a pointer declaration

而template void f(T& param)可以推导出数组

template<typename T>
void f(T& param); // template with by-reference parameter

f(name); // T -> const char [13]
         // param -> const char (&)[13]

顺带一提,用模板参数size_t可以实现获得数组大小的方法(compile-time)

// return size of an array as a compile-time constant. (The
// array parameter has no name, because we care only about
// the number of elements it contains.)
template<typename T, std::size_t N> // see info
constexpr std::size_t arraySize(T (&)[N]) noexcept
{ 
      return N; 
} 

int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 };
int mappedVals[arraySize(keyVals)];
std::array<int, arraySize(keyVals)> mappedVals;

函数类型同样会发生退化,成函数指针

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)

小总结
读书笔记 《Effective modern C++》之 Deducing Types & auto(一)

Item 2: 理解auto关键字的推断

auto和模板的推断非常相似

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.)

// 万能引用引申到auto关键字
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[] = "R. N. Briggs"; // name's type is const char[13]

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)

https://onlinegdb.com/BydZ_djxw

#include <bits/stdc++.h>

int main()
{
    auto a {41};
    auto b = {41};
    std::cout << std::boolalpha 
              << std::is_integral<decltype(a)>::value << "\n"
              << std::is_integral<decltype(b)>::value;
    return 0;
}
// true
// false

花括号的初始化方式也是auto和模板推导的唯一区别

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
                     // main.cpp:13:20: error: returning initializer list

std::vector<int> v;
auto resetV = [&v](const auto& newValue) { v = newValue; }

此外,在C++14以后,函数和lambda的参数类型也可以用auto修饰(不接受std::initializer_list)。

小总结

读书笔记 《Effective modern C++》之 Deducing Types & auto(一)

Item 3: 理解decltype

const int i = 0; // decltype(i) is const int
bool f(const Widget& w); // decltype(w) is const Widget&
// decltype(f) is bool(const Widget&)
struct Point {
int x, y; // decltype(Point::x) is int
}; // decltype(Point::y) is int
Widget w; // decltype(w) is Widget
if (f(w)) … // decltype(f(w)) is bool
vector<int> v; // decltype(v) is vector<int>
if (v[0] == 0)  // decltype(v[0]) is int&, []操作符返回的是引用

特别的是vector中v[0]得到的不是bool&是一个代理类。

c++11可以使用decltype来得到返回类型,这时返回类型auto并不进行类型推导
https://onlinegdb.com/BkX_yKjeD

#include <iostream> // std::cout
#include <type_traits> // std::is_same
#include <vector>

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

template<typename Container, typename Index> 
auto authAndAccess14(Container& c, Index i)    
{
    return c[i];
}

template<class T1, class T2>
void print_is_same() {
  std::cout << std::boolalpha << std::is_same<T1, T2>() << '\n';
}

int main()
{
    std::vector<float> vec {1.,2.,3.,5.};
    
    typedef decltype(authAndAccess(vec, 2.)) ReturnType;
    typedef decltype(authAndAccess14(vec, 2.)) ReturnType14;
    
    print_is_same<float, std::remove_reference<ReturnType>::type>(); // true
    print_is_same<float, ReturnType>();                              // false
    
    print_is_same<float, std::remove_reference<ReturnType14>::type>();// true
    print_is_same<float, ReturnType14>();                             // true
}

但是因为item1里面的原理,模板推断会忽略reference-ness,所以让auto自动推断返回类型时,[]操作符返回的T&会退化成T。如果使用C++14的返回值推导,一些操作,如authAndAccess(d, 5) = 10;会成为异常。这个问题可以通过decltype(auto)来解决,如实返回相应的c[i]类型。

https://onlinegdb.com/HkXOeFjeD

template<typename Container, typename Index> 
decltype(auto)
authAndAccess(Container& c, Index i) 
{                                     
      return c[i];
}

decltype(auto)还能用在声明时候变量的类型推导

Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // auto type deduction:
                     // myWidget1's type is Widget
decltype(auto) myWidget2 = cw; // decltype type deduction:
                               // myWidget2's type is
                               // const Widget&

但是还是存在一个问题,我们无法传入右值,因为Container&是推断不了右值的,我们还需要一个万能引用的版本。const T& 会把右值推到成T const &。同样也可能产生dangling的问题。

https://onlinegdb.com/H1G4sFjgD

template<typename Container, typename Index> 
decltype(auto) authAndAccess14_2_const(const Container& c, Index i)    
{
    return c[i];
}

typedef decltype(authAndAccess14_2_const(std::vector<float> {1.,2.,3.,5.}, 2.)) ReturnType14_2_const;

print_is_same<float, std::remove_const<
                     std::remove_reference<ReturnType14_2_const>::type
                     >::type>();  // true, remove_const和remove_reference顺序决定是float const &或是const float&

print_is_same<float, std::remove_reference<ReturnType14_2_const>::type>(); // false

print_is_same<float, std::remove_reference<
                 std::remove_const<ReturnType14_2_const>::type // false
                 >::type>();
print_is_same<float, ReturnType14_2_const>();  // false

c++11的解决办法

template<typename Container, typename Index> 
auto authAndAccess(Container&& c, Index i) 
  -> decltype(std::forward<Container>(c)[i])
{
      return std::forward<Container>(c)[i];
}
/******************************************************************************

                              Online C++ Compiler.
               Code, Compile, Run and Debug C++ program online.
Write your code in this editor and press "Run" button to compile and execute it.

*******************************************************************************/
#include <iostream> // std::cout
#include <type_traits> // std::is_same
#include <vector>
#include <typeinfo> // typeid
#include <cxxabi.h> // abi

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

template<typename Container, typename Index> 
auto authAndAccess14(Container& c, Index i)    
{
    return c[i];
}

template<typename Container, typename Index> 
decltype(auto) authAndAccess14_2(Container& c, Index i)    
{
    return c[i];
}

template<typename Container, typename Index> 
decltype(auto) authAndAccess14_2_const(const Container& c, Index i)    
{
    return c[i];
}

template<typename Container, typename Index> // final
decltype(auto)                               // C++14
authAndAccess14_perfect(Container&& c, Index i)        // version
{
    return std::forward<Container>(c)[i];
}

template<class T1, class T2>
void print_is_same() {
  std::cout << std::boolalpha << std::is_same<T1, T2>() << '\n';
}

int main()
{
    std::vector<float> vec {1.,2.,3.,5.};
    
    typedef decltype(authAndAccess(vec, 2.)) ReturnType;
    typedef decltype(authAndAccess14(vec, 2.)) ReturnType14;
    typedef decltype(authAndAccess14_2(vec, 2.)) ReturnType14_2;
    typedef decltype(authAndAccess14_perfect(std::vector<float> {1.,2.,3.,5.}, 2.)) ReturnType14_perfect;
    typedef decltype(authAndAccess14_2_const(std::vector<float> {1.,2.,3.,5.}, 2.)) ReturnType14_2_const;
    
    print_is_same<float, std::remove_reference<ReturnType>::type>();
    print_is_same<float, ReturnType>();
    
    print_is_same<float, std::remove_reference<ReturnType14>::type>();
    print_is_same<float, ReturnType14>();
    
    print_is_same<float, std::remove_reference<ReturnType14_2>::type>();
    print_is_same<float, ReturnType14_2>();
    
    print_is_same<float, std::remove_reference<ReturnType14_perfect>::type>();
    print_is_same<float, ReturnType14_perfect>();
    
    int status;
    char *realname = abi::__cxa_demangle(typeid(ReturnType14_2_const).name(), 0, 0, &status);
    std::cout << realname << "\n";
    
    print_is_same<float, std::remove_reference<ReturnType14_2_const>::type>();
    print_is_same<float, std::remove_const<
                         std::remove_reference<ReturnType14_2_const>::type
                         >::type>();
    print_is_same<float, std::remove_reference<
                     std::remove_const<ReturnType14_2_const>::type
                     >::type>();
    print_is_same<float, ReturnType14_2_const>();
}

decltype(())

x为左值时,decltype((x))会推导出T&

decltype(auto) f1()
{
    int x = 0;
    return (x); // decltype((x)) is int&, so f2 returns int&
}

decltype(auto) f2()
{
    int x = 0;
    return (x); // decltype((x)) is int&, so f2 returns int&
}

读书笔记 《Effective modern C++》之 Deducing Types & auto(一)
读书笔记 《Effective modern C++》之 Deducing Types & auto(一)

Item 4: 查看推导出的类型

Runtime Output

std::cout << typeid(x).name() << '\n'; // typeid(T)也是可以的,本质是一个操作符

// gcc
char* name = abi::__cxa_demangle(typeid(const int&).name(), nullptr, nullptr, nullptr);
std::cout << name << std::endl; // int, cv限定符会被省略,还有ref
free(name);

使用decltype帮助返回值推断的一个例子
https://*.com/questions/32885112/template-function-to-return-tuple-values-different-types-via-enum-lookup
decltype
https://*.com/questions/36762832/why-is-the-result-of-decltypeij-not-an-rvalue-reference
meta-programming得到完整类型
https://orzz.org/cxx-get-the-name-of-the-given-type/

第二章 auto

Item 5: 优先选择auto而不是explicit的类型申明

std::unordered_map<std::string, int> m;

// std::vector<int>::size_type是64bits, unsigned是32bits,混用有时候会出问题

// 如果忘记是const pair,则需要多一次复制,影响性能,而使用auto能解决这个问题。
for (const std::pair<std::string, int>& p : m) {/*do sth*/} 

读书笔记 《Effective modern C++》之 Deducing Types & auto(一)

Item 6: 如果auto没有得到想要的类型,则使用explicit的类型申明

比如 std::vector的operator[]并不是返回bool&而是一个std::vector::reference,可能会导致意想不到的结果。一些库的操作也会得到中间的代理类,比如Matrix sum = m1+m1;m1+m2可能是Sum<Matrix, Matrix>而不是Matrix类,这时候auto得到的就是 Sum<Sum<Sum<Matrix, Matrix>, Matrix>, Matrix>。

读书笔记 《Effective modern C++》之 Deducing Types & auto(一)

上一篇:C++ 关键字decltype


下一篇:Windows [和Linux]上的C [[gnu :: visibility(“default”)]] vs __declspec(dllexport)