深入了解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]
  1. 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_backmatrix尾部。

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与函数模板之推导规则辨析的主要内容,如果未能解决你的问题,请参考以下文章

简化代码,提高效率:C++ auto关键字的魅力

C++中类与函数的模板类型推导?

c++11模板类型推导规则

c++11模板类型推导规则

C++之auto关键字

001-EMC 深入解读-理解模板型别推导