玩命加载中 . . .

02-理解auto类型推导


auto类型推导和模板类型推导有一个直接的映射关系。它们之间可以通过一个非常规范非常系统化的转换流程来转换彼此

如果有这样的模板

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

调用形式为

f(expr);

f的调用中,编译器使用expr推导TParamType的类型

当一个变量使用auto进行声明时,auto扮演了模板中T的角色,变量的类型说明符扮演了ParamType的角色

考虑这个例子:

auto x = 27;

这里x的类型说明符是auto自己,另一方面,在这个声明中:

const auto cx = x;

类型说明符是const auto

再一个:

const auto& rx = cx;

类型说明符是const auto&。在这里例子中要推导xrxcx的类型,编译器的行为看起来就像是认为这里每个声明都有一个模板,然后使用合适的初始化表达式进行调用

template<typename T>            //概念化的模板用来推导x的类型
void func_for_x(T param);

func_for_x(27);                 //概念化调用:
                                //param的推导类型是x的类型

template<typename T>            //概念化的模板用来推导cx的类型
void func_for_cx(const T param);

func_for_cx(x);                 //概念化调用:
                                //param的推导类型是cx的类型

template<typename T>            //概念化的模板用来推导rx的类型
void func_for_rx(const T& param);

func_for_rx(cx);                //概念化调用:
                                //param的推导类型是rx的类型

auto类型推导除了一个例外(下面会讨论),其他情况都和模板类型推导一样

Item1描述的三个情景稍作修改就能适用于auto

  • 情景一:类型说明符是一个指针或引用但不是通用引用
  • 情景二:类型说明符一个通用引用
  • 情景三:类型说明符既不是指针也不是引用

我们早已看过情景一和情景三的例子:

auto x = 27;                    //情景三(x既不是指针也不是引用)
const auto cx = x;              //情景三(cx也一样)
const auto & rx=cx;             //情景一(rx是非通用引用)

情景二像你期待的一样运作:

auto&& uref1 = x;               //x是int左值,
                                //所以uref1类型为int&
auto&& uref2 = cx;              //cx是const int左值,
                                //所以uref2类型为const int&
auto&& uref3 = 27;              //27是int右值,
                                //所以uref3类型为int&&

Item1讨论并总结了对于non-reference类型说明符,数组和函数名如何退化为指针。那些内容也同样适用于auto类型推导:

const char name[] = "R. N. Briggs";  //name的类型是const char[13]

auto arr1 = name;               //arr1的类型是const char*
auto& arr2 = name;              //arr2的类型是const char(&)[13]

void someFunc(int, double);     //someFunc是一个函数,
                                //类型为void(int, double)

auto func1 = someFunc;          //func1的类型是void (*)(int, double)
auto& func2 = someFunc;         //func2的类型是void (&)(int, double)

下面开始不同点,如果你想声明一个带有初始值27的int,C++98提供两种语法选择:

int x1 = 27;
int x2(27);

C++11由于也添加了用于支持统一初始化(uniform initialization)的语法:

int x3 = { 27 };
int x4{ 27 };

总之,这四种不同的语法只会产生一个相同的结果:变量类型为int值为27

如果换成auto就可能不一样了

auto x1 = 27;                   //类型是int,值是27
auto x2(27);                    //同上
auto x3 = { 27 };               //类型是std::initializer_list<int>,值是{ 27 }
auto x4{ 27 };                  //同上

前面两个语句确实声明了一个类型为int值为27的变量,但是后面两个声明了一个存储一个元素27的std::initializer_list<int>类型的变量

  • 这就造成了auto类型推导不同于模板类型推导的特殊情况。当用auto声明的变量使用花括号进行初始化,auto类型推导推出的类型则为std::initializer_list

对于花括号的处理是auto类型推导和模板类型推导唯一不同的地方。当使用auto声明的变量使用花括号的语法进行初始化的时候,会推导出std::initializer_list<T>的实例化,但是对于模板类型推导这样就行不通:

auto x = { 11, 23, 9 };         //x的类型是std::initializer_list<int>

template<typename T>            //带有与x的声明等价的
void f(T param);                //形参声明的模板

f({ 11, 23, 9 });               //错误!不能推导出T

然而如果在模板中指定Tstd::initializer_list<T>而留下未知T,模板类型推导就能正常工作:

template<typename T>
void f(std::initializer_list<T> initList);

f({ 11, 23, 9 });               //T被推导为int,initList的类型为std::initializer_list<int>

因此auto类型推导和模板类型推导的真正区别在于,auto类型推导假定花括号表示std::initializer_list而模板类型推导不会这样


再补充一些,C++14允许auto用于函数返回值并会被推导,而且C++14的lambda函数也允许在形参声明中使用auto,但是在这些情况下auto实际上使用模板类型推导的那一套规则在工作,而不是auto类型推导,所以说下面这样的代码不会通过编译

auto createInitList()
{
    return { 1, 2, 3 };         //错误!不能推导{ 1, 2, 3 }的类型
}

std::vector<int> v;
auto resetV = [&v](const auto& newValue){ v = newValue; };        //C++14
resetV({ 1, 2, 3 });            //错误!不能推导{ 1, 2, 3 }的类型

请记住:

  • auto类型推导通常和模板类型推导相同,但是auto类型推导假定花括号初始化代表std::initializer_list,而模板类型推导不这样做
  • 在C++14中auto允许出现在函数返回值或者lambda函数形参中,但是它的工作机制是模板类型推导那一套方案,而不是auto类型推导

文章作者: kunpeng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kunpeng !
  目录