有时候,函数模板类型推导是个让人困惑的玩意儿~
引言
顾名思义,这篇文章的主题是函数模板类型推导,所以不会涉及过多的理论,主要研究的内容是编译器的推导行为。那么如何研究呢?实际上就是引入一些实验代码,来观察编译器的行为,所以这个过程略微有点无聊。
好了,废话不多说,直接进入主题了。
我们以函数模板中的参数传递方式为差异点来进行讨论,对应的我们可以得到四种传递方式。需要注意的是,这只是我们的分类方法,这与普通函数中的四种参数传递方式是不同的。尽管可能存在相似的地方,但本质还是不一样的。
同时还需要注意的是,函数模板类型推导是在编译期完成的。
❗ 注意:本文所使用的编译器是 MSVC2019 自带的编译器,不同编译器的推导结果可能会存在差异。
值传递
第一种方式就是值传递,对应的模板形式:
1 | template <typename T> |
然后我们针对常见的参数类型,进行测试:
1 |
|
观察上面的结果,可以发现在值传递方式推导模板实参的一些特点:
- 可以传递左值,也可以传递右值,但无法推导出引用,只能推导出原始类型。因为模板实例化生成的函数是值传递的,函数形参是函数实参的拷贝。
- 对于指针和引用,顶层 const 会被忽略掉,底层 const 不会被忽略掉。原因同上,模板实例化后的函数是值传递的,函数形参是函数实参的拷贝,在函数内直接修改函数形参,不会对函数实参有任何改变;对应的,底层 const 不能忽略,不能在函数内通过函数形参修改其所指对象的值。
- 如果数组和函数名被传递给模板,则推导出来的模板实参是对应的指针。
左值引用传递
第二种方式是左值引用传递,对应的模板形式:
1 | template <typename T> |
同样可以进行测试:
1 |
|
观察上面的结果,我们也可以得到左值引用传递方式推导模板实参的一些特点:
- 可以传递左值,无法传递右值,也无法推导出引用,但本身就是
T&,最终t的类型可能是左值引用。 - 顶层 const 和底层 const 都不会被忽略,且与实参基本一致,如果实参是 const 的,那么
T就是 const 的。 - 传递数组和函数名时,推导出来的是对应的类型,不是指针。
常量左值引用传递
第三种方式是常量左值引用传递,对应的模板形式:
1 | template <typename T> |
同样进行测试:
1 |
|
观察上面的结果,我们也可以得到常量左值引用传递方式推导模板实参的一些特点:
- 可以传递左值,也可以传递右值,也无法推导出引用,但本身就是
T&,最终t的类型可能是左值引用。从语义上讲,这与常量左值引用的语义也是符合的。 - 顶层 const 会被忽略掉,因为本身就是
const T&形式,所以即便推导T时,忽略了顶层 const,但最终又加上了顶层 const;对应的,底层 const 会被保留。 - 传递数组和函数名时,推导出来的是对应的类型,不再是指针。
右值引用传递
第四种方式是右值引用传递,对应的模板形式:
1 | template <typename T> |
同样进行测试:
1 |
|
观察上面的结果,我们也可以得到右值引用传递方式推导模板实参的一些特点:
- 可以传递左值,也可以传递右值,且会被推导为原始类型的引用。
- 顶层 const 和底层 const 都会被保留,且会被推导为原始类型的引用。
- 传递数组和函数名时,推导出来的是对应的类型,不再是指针,且会被推导为原始类型的引用。
- 为了避免出现引用的引用,对应
t的类型会发生引用折叠。
总结
我们根据函数模板的参数传递方式,讨论了四种不同传递方式的特点。可以发现:
- 右值引用可以最大限度的保留所传递参数的类型和值类别,且会发生引用折叠现象。
- 值传递方式下,顶层 const 会被忽略,底层 const 不会被忽略;数组和函数名会被传递为指针。
- 模板根据实参推导类型时,不会对实参进行隐式类型转换,只会按照模板实例化出不同函数。
还需要额外说明的是,本文的内容不需要刻意记忆,遇到了查一下就可以了。
另外,本文也可以作为理解完美转发这篇文章的补充内容。