(Effective Modern C++) – 第一章

Item 1:Understand template type deduction.

Case 1: ParamType is a Reference or Pointer, but not a Universal Reference

通用的引用的形式:const T&可以绑定到左值变量(T&)右值变量(T&&)。

  1. ref-模板的定义
template<typename T>
void f(T& param);       // param is a reference

模板的实例化的参数(例如T)会包含const限定符。但是传递给f的参数的引用与普通变量是没有区别的。

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

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. cons-ref模板定义

template<typename T>
void f(const T& param);  // param is now a ref-to-const

模板类型的实参变量的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. 指针类型同上。

Case 2: ParamType is a Universal Reference

模板的参数类型使用T &&来进行引用折叠。这样出入的参数的左/右值属性得以保留。T的const限定符予以保留。

Case 2: ParamType 既不是指针也不是引用
template<typename T>
void f(T param);         // param is now passed by value

在模板的实例化中,param会丢弃cons/volatilet限定符(cv-qualifier)(即使实参是const限定的)。param会被拷贝,所以也不会推导为引用

Array Arguments(数组参数)

例如处理一个字符串的函数f

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

如果使用引用那么T的类型会被推导为char[n]。如果使用参数T param,那么会被推导为(退化为)char*。

这个函数可以在编译期得到数组的长度

// 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  // below on
{                                                   // constexpr
  return N;                                         // and
}
Function Arguments(函数参数)

函数作为模板的参数T&,T被推导为函数的签名。T 推导为函数指针类型。

注意:

Things to Remember
• During template type deduction, arguments that are references are treated as
non-references, i.e., their reference-ness is ignored.
• When deducing types for universal reference parameters, lvalue arguments get
special treatment.
• When deducing types for by-value parameters, const and/or volatile argu‐
ments are treated as non-const and non-volatile.
• During template type deduction, arguments that are array or function names
decay to pointers, unless they’re used to initialize references.

Item 2: Understand auto type deduction.

单纯使用auto推导的是表达式的类型。不包括cv限定符以及引用的属性。所以需要自行加入限定符、引用属性。如果加上了&或&&,那么引用折叠还是会发生的。

分为三种情况:

Case1:表达式是引用以及非引用情况

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&&
Case3:数组参数

加引用推导为数组的类型:T[n];不带有引用的auto退化为指针类型T*。函数指针也是。

花括号列表初始器在auto下的推导:
auto x1 = 27;             // type is int, value is 27
auto x2(27);              // ditto
auto x3 = { 27 };         // type is std::initializer_list<int>,
                          // value is { 27 }
auto x4{ 27 };            // ditto 同上

初始化列表:

auto x5 = { 1, 2, 3.0 };  // error! can't deduce T for
                          // std::initializer_list<T>

{}在类型推导过程中推导为std::initializer_list<T>类型。

所以模板的参数定义

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

However, if you specify in the template that param is a std::initializer_list<T>
for some unknown T, template type deduction will deduce what T is:

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>

C++14 auto作为匿名函数的参数的类型

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 }
Things to Remember
• auto type deduction is usually the same as template type deduction, but auto
type deduction assumes that a braced initializer represents a std::initializer_list, and template type deduction doesn’t.
• auto in a function return type or a lambda parameter implies template type deduction, not auto type deduction.
Item 3: Understand decltype

可以在编译期推导表达式的类型。他不会计算表达式。

例如编写一个使用[]运算符返容器的某个元素的引用时,使用decltype可以避免返回一些意外的代理类(例如std::vector<bool>的operator[])

//C++14 版本
template<typename Container, typename Index>    // C++14;
auto authAndAccess(Container& c, Index i)       // not quite
{                                               // correct
  authenticateUser();
  return c[i];                  // return type deduced from c[i]
}
//尾置返回类型
template<typename Container, typename Index>    // works, but
auto authAndAccess(Container& c, Index i)       // requires
  -> decltype(c[i])                             // refinement
{
  authenticateUser();
  return c[i];
}

这样设计就会导致这样的问题出现authAndAccess(容器,1)=5的出现。如果容器是const限定的,由于auto抛弃cv运算符。这就会导致无法赋值的问题。

面临的问题:

  • 容器本身的引用限定。
  • 返回值的引用类型或者cv限定。

解决方案:

  • 使用通用的引用:T&&
  • decltype(auto)可以确保返回的类型与return语句返回的类型是一致的(cv、引用)。decltype(auto)通用可以应用在变量声明上。
  • return std::forward<Container>(c)[i];语句确保函数的返回类型一致。 std::forward<Container>返回的是表达式。而decltype((x))一定是一个引用!所以返回的是表达式而非变量。这样decltype(auto)才是有效的

(注意:C++不使用delctype(auto)) 这样声明即可

auto                                               // C++11
authAndAccess(Container&& c, Index i)              // version
-> decltype(std::forward<Container>(c)[i])
#include <iostream>
#include <vector>

class Widget
{
public:
	Widget()
	{

	}
	Widget(int)
	{

	}
	operator bool()
	{
		return true;
	}
};
template<typename Container, typename Index>
auto authAndAccess(Container &c, Index i)->decltype(c[i]) //c i 在函数名定义之前是不可用的
{
	if (i >= 0 && i<c.size())
		return c[i];
	else
		if (i < 0)
			return c[-i % c.size()];
		else
			return c[i % c.size()];
}
//C++14 从函数中推导
template<typename Container, typename Index>
decltype(auto) authAndAccess1(Container &c, Index i) //c i 在函数名定义之前是不可用的
{
	if (i >= 0 && i<c.size())
		return c[i];
	else
		if (i < 0)
			return c[-i % c.size()];
		else
			return c[i % c.size()];
}
//改进支持lvalue和rvalue
template<typename Container, typename Index>
decltype(auto) authAndAccess2(Container &&c, Index i) //c i 在函数名定义之前是不可用的
{
	if (i >= 0 && i<c.size())
		return c[i];
	else
		if (i < 0)
			return c[-i % c.size()];
		else
			return c[i % c.size()];
}
//改进的,支持返回指定的类型
template<typename Container, typename Index>
decltype(auto) authAndAccess3(Container &&c, Index i) //c i 在函数名定义之前是不可用的
{
	if (i >= 0 && i<c.size())
		return std::forward<Container>(c)[i];
	else
		if (i < 0)
			return std::forward<Container>(c)[-i % c.size()];
		else
			return std::forward<Container>(c)[i % c.size()];
}
//C++11 版本:改进的,支持返回指定的类型
template<typename Container, typename Index>
auto authAndAccess4(Container &&c, Index i)->decltype(std::forward<Container>(c)[i]) //c i 在函数名定义之前是不可用的
{
	if (i >= 0 && i<c.size())
		return (c[i]);
	else
		if (i < 0)
			return (c[-i % c.size()]);
		else
			return (c[i % c.size()]);
}
std::vector<int> makeIntVector()
{
	return std::vector<int>{2, 3};
}
int main()
{
	std::vector<int> vec{ 1,2,3 };
	std::cout << authAndAccess(vec, 2) << std::endl;
	std::cout << authAndAccess(vec, -4) << std::endl;
	/*----------------*/
	authAndAccess1(vec, -4) = 50;
	std::cout << authAndAccess1(vec, -4) << std::endl;
	//如果传入的是临时的对象
	auto &s = authAndAccess3(std::move(makeIntVector()), 1);
	s = 20;
	std::cout << s << std::endl;
	//C++11 版本的调用
	auto s1 = authAndAccess4(makeIntVector(), 2);
	s1 = 21;
	std::cout << s1 << std::endl;
	std::cout << (authAndAccess4(makeIntVector(), 2) = 0) << std::endl;//也是右值引用
	auto io = makeIntVector();
	auto &&ij = std::forward<int>(6+6);
	auto &&iij = std::forward<std::vector<int>>(std::move(io));//iij本身是右值引用
	system("pause");
	return 0;
}
 Things to Remember
• decltype almost always yields the type of a variable or expression without
any modifications.
• For lvalue expressions of type T other than names, decltype always reports a
type of T&.
• C++14 supports decltype(auto), which, like auto, deduces a type from its
initializer, but it performs the type deduction using the decltype rules.
Item 4: Know how to view deduced types.

运行时typeid关键字返回的类型在模板函数:

#include <typeinfo>
#include <iostream>
#include <vector>

struct ww{
    ww(int){}
};
template<typename T>
void f(const T &param)
{
    using std::cout;
    cout<<"T    ="<<typeid(T).name()<<"\n";//类型显示的是一样的,因为typeid是模板函数,推导的时候使用T,所以const、volatile
    cout<<"param="<<typeid(param).name()<<"\n";
}
std::vector<ww> createVec()
{
    return std::vector<ww>{2,3,5};
}


int main()
{
    const auto vw = createVec();
    if(!vw.empty())
    {
        f(&vw[0]);
    }
    system("pause");
}

显示T =PK2ww,param=PK2ww。但是param的类型与T完全不同所以,typeid的返回的结果并不一定准确。

typeid 运算符在应用于多态类类型的左值时执行运行时检查,其中对象的实际类型不能由提供的静态信息确定。 此类情况是:

  • 对类的引用
  • 使用 * 取消引用的指针
  • 带下标的指针(即 [ ])。(请注意,通常情况下,将下标与指向多态类型的指针一起使用不安全。)

typeid在多态的情况下:类有虚函数->运行时求值。否则返回的是静态类型。

如果typeid(exp)的表达式:

  • exp为指针则会返回<指针所指的静态类型>*(编译时),返回指针的type_info。不管类是否有虚函数。指针并不是类类型
  • exp为指针的解引用,检查所指对象的静态类型,是否有虚函数。然后决定什么时候求值【不包含虚函数的类型:静态编译,且不计算*exp;否则,动态编译】。动态编译时,如果指针是nullptr,typeid 将引发 bad_typeid 异常
  • 如果 expression 既不是指针也不是对对象的基类的引用,则结果是表示 expression 的静态类型的 type_info 引用。 表达式的 static type 将引用在编译时已知的表达式的类型。 在计算表达式的静态类型时,将忽略执行语义。 此外,在确定表达式的静态类型时,将忽略引用(如果可能):
// expre_typeid_Operator_2.cpp  
#include <typeinfo>  
  
int main()  
{  
   typeid(int) == typeid(int&); // evaluates to true  必须是编译时,所以int&也变成了int
}
  • 对类的引用,返回的总是不带有引用的类型(引用本身是一个别名)
引用&参考: