C++活到老学到老 auto

起因

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++,活到老学到老。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值