深入了解C++:auto与函数模板之推导规则辨析
Posted loOK后端
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入了解C++:auto与函数模板之推导规则辨析相关的知识,希望对你有一定的参考价值。
❝观前提醒,小屏手机横屏有助于提升阅读体验
❞
C++11引入了自动类型推导auto
,其类型推导规则如下:
1.auto
的推导规则基本和【】一文中函数模板中的参数推导规则一致。
「值传递」
在auto
不加上任何修饰符以及引用时,推导变量类型,其推导规则和基于值传递的函数模板推导规则一致:
-
复制:如果是常量会优先使用移动构造函数。 -
退化
demo演示如下:
int num=10;
const int num_const = 100;
const int& num_ref = num;
auto num_0 = 10; // 值传递:1o是内置int类型,直接复制
auto foo_auto = Foo{100}; // 禁止优化时: 一次默认构造函数 + mtor
auto num_1 = num; // 值传递:复制
auto num_2 = num_const; // 值传递:退化为 int
auto num_3 = num_ref; // 值传递,退化为 int
auto num_4 = "hello auto"; // const char*
「引用传递」
const auto&
、auto&
以及auto&&
的推导规则和函数模板的三种引用传递形式的推导规则一一对应:
// const T&
const auto& num_const_auto_ref_1 = num_const; // const int&
const auto& num_const_auto_ref_2 = num_ref; // const int&
const auto& num_const_arr_3 = "hello"; // const char(&)[6];
// T&
auto& num_ref_1 = num_const; // const int&
auto& num_ref_2 = num_ref; // const int&
auto& num_arr = "hello"; // const char(&)[6];
// auto& num_int = 1; // error
// T&&
auto&& num_perfect_ref_1 = num; // int&
auto&& num_perfect_ref_2 = num_const; // const int&
auto&& num_perfect_ref_3 = num_ref; // const int&
auto&& num_perfect_ref_4 = 1; // int&&
auto&& num_perfect_ref_5 = "hello"; // const char[&][6]
-
auto
的推导规则,有一条和函数模板的不同,即花括号{}
。
{}
在C++11中,有两种身份:
-
统⼀初始化(uniform initialization)的标志 -
std::initializer_list
类的的标志
在C++11后,如果你想定义一个int
类型变量int_num
,并使用{1}
来对int_num
进行初始化。有两种初始化方式:
-
用 int
来显式地表达变量int_num
的类型 -
用 auto
来推导变量int_num
的类型
得到的结果却完成不一样:
int int_num_1 = {1}; // 推导为:int
auto int_num_2 = {1}; // 推导为:std::initializer_list<int>{1}
也就是说,使用{...}
给auto
定义的类型变量初始化,最终会得到一个std::initializer_list<T>{...}
对象。
「by the way」
但是当auto
用于推导函数参数的类型时,就和函数模板一样,也无法识别{...}
。如下一个lambda
函数,auto
用于推导输入参数类型,输入参数是{1,2,3,4}
时,编译错误:
[](auto init_list){ /**空函数体**/ } ({1,2,3,4}); // 编译 error
[](auto init_list){ /**空函数体**/ } (std::initializer_list<int>{1,2,3,4}); // 编译 ok
此外,当auto
用于推导函数返回值时,也是无法推导出{...}
的返回类型:
// 编译 error
auto make_init_list() {
return {1,2,3,4};
}
甚至在知乎上就有个因为{...}
无法推导的问题:
来解答下这个问题。对于二维数组matrix
:
std::vector<std::vector<int>> matrix;
matrix.push_back({1,2,3}); // (1) 编译 Ok
matrix.emplace_back({1,2,3}); // (2) 编译 Error
「push_back」
(1)
这行代码能通过,是因为std::vector<int>
有个以std::initializer_list<int>
为对象的构造函数。当你传入{1,2,3}
的时候会先基于{1,2,3}
通过隐式转换来构造一个临时std::vector<int>
对象,再把这个临时对象push_back
到matrix
尾部。
用gdb
调试在(1)
处打断点,可以看到会跳入基于 initializer_list
对象的vector
的构造函数中:
vector(initializer_list<value_type> __l, const allocator_type& __a = allocator_type())
: _Base(__a)
{
_M_range_initialize(__l.begin(),
__l.end(),
random_access_iterator_tag());
}
「emplace_back」
但是为什么emplace_back
就不能编译正确?因为emplace_back
函数的参数类型是基于模板参数类型推导,此时你输入的{1,2,3}
模板无法识别为std::initializer_list<int>{1,2,3}
,那么就i无法调用合适的std::vector<int>
构造函数,因此会编译报错。
void vector<_Tp, _Alloc>::emplace_back(_Args&&... __args) {
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
_GLIBCXX_ASAN_ANNOTATE_GROW(1);
_Alloc_traits::construct(this->_M_impl,
this->_M_impl._M_finish,
std::forward<_Args>(__args)...);
++this->_M_impl._M_finish;
_GLIBCXX_ASAN_ANNOTATE_GREW(1);
}
else
_M_realloc_insert(end(), std::forward<_Args>(__args)...);
}
因此,正确的写法,有如下三种:
matrix.emplace_back<std::vector<int>>({1,2,3});
matrix.emplace_back(std::vector<int>{1,2,3});
matrix.emplace_back(std::initializer_list<int>{1,2,3});
本质上,都是在告诉编译器,这个{1,2,3}
是用于构造std::vector<int>
的临时对象,那么{1,2,3}
会被推导为std::initializer_list
类型,然后通过隐式转换用于构造std::vector<int>
临时对象。
「总结」
对auto
的推导规则,总结如下:
-
auto
的推导规则与函数模板的推导规则基本一致; -
仅当 auto
定义的变量使用{}
初始化时,才会将变量的类型推导为std::initializer_list
。然而,当auto
用于函数参数、返回值类型推导时,和函数模板一样,无法识别{}
。
哎呦!奇怪的C++知识又增加了。
如果有什么意见,或者想知道的C++知识,可以在后台给我发消息告诉我。
感谢你的观看,你的点赞、关注与分享就是对我最大的支持。
以上是关于深入了解C++:auto与函数模板之推导规则辨析的主要内容,如果未能解决你的问题,请参考以下文章