彻底理解c++的隐式类型转换 (4)

还有另外一点要注意,标准规定了如果用户转换序列转换出了一个左值(比如一个左值引用),而最终转换目标的右值引用,那么标准转换中的左值转换为右值的规则不可用,程序是无法通过编译的,比如:

struct A { operator int&(); }; int&& b = A();

编译上面的代码,g++会奖励你一句cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’。

如果隐式转换序列里一个可行的转换都没有呢?那很遗憾,只能编译报错了。

隐式转换引发的问题

现在我们已经知道隐式转换的工作方式了。而且我们也看到了隐式类型转换是如何闯祸的。

下面将要介绍隐式类型转换闯了祸怎么善后,以及怎么防患于未然。

是时候和实际应用碰撞出点火花了。

引用绑定

第一个问题是和引用相关的。不过与其说是隐式转换惹的祸倒不如说是引用绑定自身的坑。

我们知道对于一个类型T,可以有这几种引用类型:

T&,T的引用,只能绑定到T类型的左值

const T&,const T的引用,可以绑定到T的左值和右值,以及const T的左值和右值

T&&,T的右值引用,只能绑定到T类型的右值

const T&&,一般来说见不到,然而当你对一个const T&使用std::move就能得到这东西了

引用必须在声明的同时进行初始化,所以下面这样的代码应该是大家再熟悉不过的了:

int num = 0; const int &a = num; int &b = num; int &&c = 100; const int &d = 100;

新的问题出现了,考虑一下如下代码的运行结果:

int a = 10; long &b = a; std::cout << b << std::endl;

不是10吗?还真不是:

c.cpp: In function ‘int main()’: c.cpp:6:11: error: cannot bind non-const lvalue reference of type ‘long int&’ to an rvalue of type ‘long int’ 6 | long &b = a; |

报错说得很清楚了,一个普通的左值引用不能绑定到一个右值上。因为a是int,b是long,所以a想赋值给b就必须先隐式转换成long。

隐式转换除非是转成引用类型,否则一般都是右值,所以这里报错了。解决办法也很简单:

long b1 = a; const long &b2 = a;

要么直接复制构造一个新的long类型变量,值类型的变量可以从右值初始化;要么使用const左值引用,因为它能绑定到右值。

扩展一下,函数的参数传递也是如此:

void func(unsigned int &) { std::cout << "lvalue reference" << std::endl; } void func(const unsigned int &) { std::cout << "const lvalue reference" << std::endl; } int main() { int a = 1; func(a); }

结果是“const lvalue reference”,这也是为什么很多教程会叫你尽量多使用const lvalue引用的原因,因为除了本身的类型T,这样的函数还可以通过隐式类型转换接受其他能转换成T的数据做参数,并且相比创建一个对象并复制初始化成参数,应用的开销更小。当然右值最优先匹配的是右值引用,所以如果有形如void func(unsigned int &&)的重载存在,那么这个重载会被调用。

最典型的应用非下面的例子莫属了:

template <typename... Args> void format_and_print(const std::string &s, Args&&... args) { // 实现格式化并打印出结果 } std::string info = "%d + %d = %d\n"; format_and_print(info, 2, 2, 4); format_and_print("%d * %d = %d\n", 2, 2, 4);

只要能隐式转换成string,就能直接调用我们的函数。

最重要的一点,隐式类型转换产生的通常是右值。(当然显式类型转换也一样,不过在隐式转换的时候更容易忘了这点)

数组退化

同样是隐式转换带来的经典问题:数组在求值表达式中退化成指针。

你能给出下面代码的输出吗:

void func(int arr[]) { std::cout << (sizeof arr) << std::endl; } int main() { int a[100] = {0}; std::cout << (sizeof a) << std::endl; func(a); }

在我的amd64 Linux上使用GCC 10.2编译运行的结果是400和8,后者其实是该系统上int*的大小。因为sizeof不求值而函数参数传递是求值的,所以数组退化成了指针。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxssx.html