模板参数是否可以是引用类型?



我已经开始学习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;
}

并指出此定义有一个缺点,因为T1T2可能是引用,因此返回类型可以是引用类型。

但是,第二本书指出,如果ParamType,在我们的例子中T1T2既不是指针也不是引用,对于我们的情况也是如此,则调用表达式的引用部分将被忽略。

用示例说明

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

  1. 如果 E2 和 E3 是相同类型和相同值类别的 gl值,则结果具有相同的类型和值类别 [...]
  1. 否则,结果是一个 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 中至少有一个是位字段,则为位字段。

因此,ab是左值,如果它们属于同一类型b<a?a:b也将是左值。在这里看到一个很好的解释。

这意味着max(1, 2)调用的返回类型将为int&,而max(1, 2.0)的返回类型将为int

最新更新