起因
2020年了,C++20似乎已经正式发布了,主流编译器应该都支持了吧。从C++11开始,每三年都会正式发布一版更新的标准,并伴随着各种新特性和标准库。变化太快有时候跟不上,是时候再回顾一下C++了,这东西活到老学到老啊。
这篇博文是受这个mediu文章启发,自己测试并总结出来的。主要对auto
关键字和相似的类型推断行为进行了探索。
经过
测试系统
Ubuntu 20.04, g++ 9.3.0。
源码在这里。
定义变量时的类型推断
相信自从C++11之后,大家都获得了这个利器,auto
。我们可以写
auto a = 0;
之前用一直没有仔细测试,对于一些litertal, auto
自动推断类型的方式和我们预想的是一致的,例如
auto a = 0; // int.
auto b = 0.0; // double.
auto c = 0.0f; // float.
auto d = "abc"; // const char*.
#include <string>
using namespace std::string_literals;
auto e = "abc"s; // std::__cxx11::basic_string<char>.
注意如果想使用string literal输入给auto
进行对std::string
的推断,需要显式using namespace std::string_literals
.
当auto
和逗号表达式结合进行变量定义时,行为是这样的
auto a = 0, b = 1; // int, int.
auto c = 0, d = 1.0; // This is an error.
也就是说不能在逗号表达式内利用类型不一致的literal初始化不同的变量。
有趣的是用auto
对序列进行初始化。auto
似乎不能显式用在像vector
一类的对象的初始化上,但是可以忽略模板参数让编译器推断类型,有点和使用auto
类似,就放在一起了。
auto a = { 0, 1, 2 }; // std::initializer_list<int>.
auto a { 0, 1, 2 }; // This is an error.
auto a = { 0, 1.0, 2 }; // This is an error.
std::vector b { 0, 1, 2 }; // std::vector<int, std::allocator<int> >.
std::vector c = a; // std::vector<int, std::allocator<int> >.
std::array d { 0, 1, 2 }; // std::array<int, 2>.
std::pair e0 { 0, 1.0 }; // std::pair<int, double>.
auto e1 = std::make_pair( 0, 1.0 ); // std::pair<int, double>.
std::tuple f0 { 0, 1.0, 2.0f }; // std::tuple<int, double, float>.
auto f1 = std::make_tuple( 0, 1.0, 2.0f ); // std::tuple<int, double, float>.
std::tuple f2 = a; // std::tuple<std::initializer_list<int> >.
std::map g { std::pair{ 0, 1.0 }, std::pair{ 2, 3.0 } };// std::map<int, double, std::less<int>, std::allocator<std::pair<const int, double> > >.
注意,上述示例中 f2
的初始化并未得到一个可用的tuple。
带有引用的变量和structured binding
使用auto&
对变量进行定义是可以的。
auto a = 0; // int.
auto& b = a; // int&.
const auto& c = a; // const int&.
auto
关键字用在structured binding中,当配合引用符号&
使用时有奇效。
测试结果表明,使用implicit类型的pair或者tuple,或者使用std::make_pair()
和std::make_tuple()
无法传递reference。并且采用const auto&
或者auto&
进行的structured binding不能得到const
类型的变量。
使用explicit类型的pair或者tupole,可以传递引用,但是采用const auto
的structured binding仍然不能获得const
类型的变量(原始类型不是const
时)。
使用std::tie()
可以用const auto& []
实现对原始变量的引用,但是失去了const
。
测试下来const auto&
进行structured binding时,不能得到const
类型的引用(原始类型不是const
时)。
// Simple std::pair. Cannot bind to a reference.
std::pair a { 0, 1.0 };
auto [ a0, a1 ] = a; // int, double.
auto& [ a2, a3 ] = a; // int, double.
const auto& [a4, a5] = a; // int, double.
auto& [a6, a7] {a}; // int, dobule.
// Simple std::tuple. Cannot bind to a reference.
std::tuple b { 0, 1.0, "2.0" }; // int, double, const char*.
auto [ b0, b1, b2 ] = b; // int, double, const char*.
auto& [ b3, b4, b5 ] = b; // int, double, const char*.
// std::make_pair(). Cannot bind to a reference.
auto [c0, c1] = std::make_pair( 0, 1.0 ); // int, double.
const auto& [c2, c3] = std::make_pair( 0, 1.0 ); // int, double.
auto d0 = 0, d1 = 1; // int, int.
const auto [d2, d3] = std::make_pair( d0, d1 ); // const int, const int.
auto [d4, d5] = std::make_pair<int&, int&>(d0, d1); // int, int.
// std::make_tuple(). Cannot bind to a reference.
auto e0 = 0, e1 = 1; // int, int.
const auto [e2, e3] = std::make_tuple(e0, e1); // const int, const int.
const auto& [e4, e5] = std::make_tuple(e0, e1); // const int, const int.
const auto [e6, e7] = std::make_tuple<int&, int&>(e0, e1); // const int, const int.
// Explicit std::pair and std::tuple can transfer reference.
std::pair<int&, int&> fp { e0, e1 }; // int&, int&.
const auto [fp0, fp1] = fp; // int&, int&. fp0 can be assigned to a new value and affect e0.
std::tuple<int&, int&> ft { e0, e1 }; // int&, int&.
const auto [ft0, ft1] = ft; // int&, int&. ft0 can be assigned to a new value and affect e0.
// Variables binded with auto are copies.
// Variables binded with auto& are references.
std::pair<int, int> gp { e0, e1 }; // int, int.
auto [gp0, gp1] = gp; // int, int. Assigning to gp0 will NOT affect gp.first.
auto& [gp2, gp3] = gp; // int&, int&. Assigning to gp2 WILL affect gp.first.
std::tuple<int, int> gt { e0, e1}; // int, int.
auto [gt0, gt1] = gt; // int, int. Assigning to gt0 will NOT affect get<0>(gt).
auto& [gt2, gt3] = gt; // int&, int&. Assigning to gt2 WILL affect get<0>(gt).
// The original variables are const.
const int ec0 = 0, ec1 = 1;
std::pair<const int&, const int&> fpc { ec0, ec1 };
auto [ fpc0, fpc1 ] = fpc; // const int&, const int&.
auto& [ fpc2, fpc3 ] = fpc; // const int&, const int&.
// std::tie() can transfer reference.
// Binding with const auto& will drop the constness.
int x = 0, y = 1; // int, int.
const auto& [x0, y0] = std::tie(x, y); // int&, int&. x0 can affect x.
const int w = 0, z = 1; // const int, const int.
const auto& [w0, z0] = std::tie( w, z ); // const int&, const int&.
auto& [ w1, z1 ] = std::tie( w, z ); // This is an error.
auto
用作函数的参数类型和返回类型
下面这个函数模板貌似是不能像我写的c0和c1那样实例化的。
template < typename T0, typename T1, typename T2 >
T2 add_two_t3( const T0& t0, const T1& t1 ) {
return t0 + t1;
}
auto a = 0;
auto b = 1.0;
auto c0 = add_two_t3(a, b); // Error.
double c1 = add_two_t3(a, b); // Error.
auto c2 = add_two_t3<int, double, double>(a, b); // OK.
原因是编译器无法推断T2的类型。但是采用auto
关键字作为返回值类型,是没问题的。
template < typename T0, typename T1>
auto add_two_t( const T0& a, const T1& b ) {
return a + b;
}
auto a = 0;
auto b = 1.0;
auto c = add_two_t(a, b); // OK.
我们甚至可以写
auto add_two( const auto& a, const auto& b) {
return a + b;
}
auto
适用于lambda函数
与普通函数的情况类似auto
可作为lambda函数的参数类型和返回类型。下面两个lambda函数的效果是一样的(function signature中不包含返回值,并且参数列表的类型有些许不同)。auto
还用于保存一个lambda函数,否则的话,很难描述一个lambda究竟是什么类型。
auto g = [](const auto& a, const auto& b) {
return a + b;
};
auto g1 = [](const auto& a, const auto& b) -> auto {
return a + b;
};
上面保存在g
或者g1
中的lambda函数,可以作为参数实例化函数模板或者传递给一个auto
类型的参数。
template < typename T0, typename T1, typename TF >
auto apply( const T0& x, const T1& y, const TF& f) {
return f(x, y);
}
auto apply_auto( const auto& x, const auto& y, const auto& f ) {
return f(x, y);
}
auto c = apply( 2.0, 3.0f, g );
auto c1 = apply_auto( 4, 5.0f, g1 );
结果
我打算继续探索C++,活到老学到老。