一篇文章学完 Effective Modern C++:条款 & 实践

条款1: 模板参数类型推导,引用折叠

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template<typename T>
void f(T&& param);

int x = 27;
const int cx = x;
const int& rx = x;

// 左值的情况
f(x);    // T 的类型为 int&, paramType 为 int&
f(cx);   // T 的类型为 const int&, paramType 为 const int&
f(rx);   // T 的类型为 const int&, paramType 为 const int&

// 右值的情况
f(27)    // T 的类型为 int, paramType 为 int&&

对于指向 const 对象的 const 指针的传递,仅有指针本身的常量性会被忽略:

1
2
3
4
5
6
template<typename T>
void f(T param);

const char* const ptr = "Fun with pointers";

f(ptr);    // T 和 param 的类型均为 const char*

按值传递给函数模板的数组类型将退化为指针类型,但按引用传递却能推导出真正的数组类型:

1
2
3
4
5
6
template<typename T>
void f(T& param);

const char name[] = "J. P. Briggs";

f(name);   // T 的类型为 const char[13], paramType 为 const char (&)[13]

利用声明数组引用这一能力可以创造出一个模板,用来推导出数组含有的元素个数:

1
2
3
4
5
6
7
template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept {
    return N;
} // constexpr 函数,表示这个函数可以在编译时计算结果

int arr[10];
std::size_t size = arraySize(arr); // size 的值是 10

函数类型同样也会退化成函数指针,并且和数组类型的规则类似:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void someFunc(int, double);

template<typename T>
void f1(T param);

template<typename T>
void f2(T& param);

f1(someFunc);   // param 被推导为函数指针,具体类型为 void (*)(int, double)
f2(someFunc);   // param 被推导为函数引用,具体类型为 void (&)(int, double)

条款2: auto类型推导

条款3:理解 decltype

在 C++11 中,decltype的主要用途是声明返回值类型依赖于形参类型的函数模板,这需要用到返回值类型尾置语法(trailing return type syntax):

1
2
3
4
5
template<typename Container, typename Index>
auto authAndAccess(Container& c, Index i) -> decltype(c[i]) {
    authenticateUser();
    return c[i];
}

C++11 允许对单表达式的 lambda 的返回值实施类型推导,而 C++14 将这个允许范围扩张到了一切函数和一切 lambda,包括那些多表达式的。这就意味着在 C++14 中可以去掉返回值类型尾置语法,仅保留前导auto
但编译器会为auto指定为返回值类型的函数实施模板类型推导,这样就会留下隐患(例如忽略初始化表达的引用性),使用decltype(auto)来说明我们采用的是decltype的规则,就可以解决这个问题:

1
2
3
4
5
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i) {
    authenticateUser();
    return c[i];
}

在初始化表达式处也可以应用decltype类型推导规则:

1
2
3
4
Widget w;
const Widget& cw = w;
auto myWidget1 = cw;            // auto 推导出类型为 Widget
decltype(auto) myWidget2 = cw;  // decltype 推导出类型为 const Widget&

在上述情形中,我们无法向函数传递右值容器,若想要采用一种既能绑定到左值也能绑定到右值的引用形参,就需要借助万能引用,并应用std::forward(参考条款 25):

1
2
3
4
5
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i) {
    authenticateUser();
    return std::forward<Container>(c)[i];
}