我已经开始学习C++,目前我正在尝试开始使用模板,所以如果我的措辞不是 100% 准确,请耐心等待。
我正在使用以下文献:
- C++模板:完整指南(第 2 版)
- 有效的现代C++:改善C++11 和 C++14 使用的 42 种具体方法
第一本书看了下面的模板函数
template<typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(b<a?a:b) {
return b < a ? a : b;
}
并指出此定义有一个缺点,因为T1
或T2
可能是引用,因此返回类型可以是引用类型。
但是,第二本书指出,如果ParamType
,在我们的例子中T1
和T2
既不是指针也不是引用,对于我们的情况也是如此,则调用表达式的引用部分将被忽略。
用示例说明
template<typename T>
void f(T param);
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // T's and param's types are both int
f(cx); // T's and param's types are again both int
f(rx); // T's and param's types are still both int
现在我想知道,第一个代码片段的返回类型怎么可能是一个引用类型?
他们都是对的:
查看 cppinsights 中生成的代码
template<typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(b<a?a:b) {
return b < a ? a : b;
}
template<typename T1, typename T2>
auto max2(T1 a, T2 b){
return b < a ? a : b;
}
max(j,i);
max2(j,i);
将"生成":
template<>
int & max<int, int>(int a, int b)
{
return b < a ? a : b;
}
template<>
int max2<int, int>(int a, int b)
{
return b < a ? a : b;
}
问题是大约 C++11-> decltype(b<a?a:b)
如果您删除它(在 C++14 及更多中),该函数将不再返回引用。更准确地说,从 C++14 开始,函数返回类型通常通过使用decltype(auto)
(不仅仅是auto
)推导,如下所示:
template<typename T1, typename T2>
decltype(auto) max(T1 a, T2 b) {
return b < a ? a : b;
}
但是,在这种情况下,我们应该只使用auto
作为返回类型,以确保我们使用基于 auto 而不是 decltype 规则的类型推导来获得我们想要的东西。
现在
static_assert( is_same_v<decltype(i),int> );
static_assert( is_same_v<decltype((i)),int&> );
static_assert( is_same_v<decltype(i+j),int> );
static_assert( is_same_v<decltype(true?i:j),int&> );
见 https://en.cppreference.com/w/cpp/language/operator_other#Conditional_operator
- 如果 E2 和 E3 是相同类型和相同值类别的 gl值,则结果具有相同的类型和值类别 [...]
- 否则,结果是一个 prvalue [...]
在C++,这意味着:
static_assert( is_same_v<decltype(true?i:j),int&> ); // E2 and E3 are glvalues
static_assert( is_same_v<decltype(true?i:1),int> ); // Otherwise, the result is a prvalue
因为 T1 或 T2 可能是引用,因此返回类型可以是引用类型。
我认为这是错误引用的。返回类型可能是引用类型,但原因不同。
如果你在第二本书中走得更远一点。在第3 项中,您将找到问题的答案。
将 decltype 应用于名称将生成该名称的声明类型。名称通常是左值表达式,但这不会影响 decltype 的行为。但是,对于比名称更复杂的左值表达式,decltype 通常确保报告的类型是左值引用。也就是说,如果名称以外的左值表达式的类型为 T,则 decltype 将该类型报告为 T&。
但是,这种行为的含义值得注意。在
int x = 0;
x 是变量的名称,因此 decltype(x) 是 int。但是,将名称 x 括在括号中("(x)")会产生比名称更复杂的表达式。作为一个名称,x 是一个左值,C++也将表达式 (x) 定义为一个左值。因此,decltype((x)) 是 int&。在名称两边加上括号可以更改为其键入报告的类型!
现在剩下的唯一问题是:
b<a?a:b
是左值吗?
从 cpp 首选项
4) 如果 E2 和 E3 是相同类型和相同值的 gl值 类别,则结果具有相同的类型和值类别,并且 如果 E2 和 E3 中至少有一个是位字段,则为位字段。
因此,a
和b
是左值,如果它们属于同一类型b<a?a:b
也将是左值。在这里看到一个很好的解释。
这意味着max(1, 2)
调用的返回类型将为int&
,而max(1, 2.0)
的返回类型将为int