【问题标题】:c++ 11 Trailing return type with decltype does not work as expectedC ++ 11尾随返回类型与DECLTTYPE不正常工作
【发布时间】:2020-05-12 10:55:13
【问题描述】:

为什么这会“按预期”工作? 我的理解是这不应该起作用:

template <class T, class U>
auto x(T a, U b) -> decltype(a<b ? a:b) {
    return a > b ? a : b;
}


int main() {
    cout << x<long, double>(1, 2.01) << endl;
    cout << x<long, double>(5, 2.01) << endl;

}

我尝试了其他一些组合,例如:

template <class T, class U>
auto x(T a, U b) -> decltype(a<b ? a:a) {
    return a > b ? a : b;
}

这样它不会编译错误实际上第二个组合失败了

compile time error:  Error C2440 'return': cannot convert from 'U' to 'T &' 

这是预期的。我的理解是第一个函数也应该失败并出现同样的错误,但它工作正常。

【问题讨论】:

  • 您的期望是什么,实际行为是什么,以及它在哪些方面未能满足您的期望?我不清楚你到底在问什么。
  • 与任何表达式一样,表达式a&lt;b ? a:b 具有某种类型,派生自ab 的类型并且独立于它们的值。您似乎期望表达式具有不同的类型,具体取决于在运行时是 a&lt;b 还是 !(a&lt;b)。这不是 C++ 的工作方式——所有类型都是在编译时确定的。
  • 请注意,如果使用两个相同类型的参数调用该函数,则该函数具有未定义的行为。不要使用它。如果您删除尾随的 decltype(...) 返回类型或在其周围添加 std::decay_t 会很好。
  • 实际上第二个组合失败并出现编译时错误:严重性代码描述项目文件行抑制状态错误 C2440 'return': cannot convert from 'U' to 'T &'
  • 我的期望是看到打印出各种类型的最大值,尤其是 double 和 long。但是如果你在 decltype 中看到 min 被写入,而它仍然计算 max。

标签: c++ c++11 templates decltype trailing-return-type


【解决方案1】:

?: 运算符的条件是什么并不重要。结果类型被计算为第二个和第三个操作数的通用类型。以下是?: 运算符的通用类型 和值类别如何计算的部分内容,详情请参阅cppreference.com

如果第二个和第三个操作数是相同类型的左值,则结果类型将是该类型的左值。

如果类型是不相关的左值,则有一些更复杂的规则来确定通用类型,但结果将是纯右值,而不是左值。特别是如果这两种类型是算术类型,例如doublelong,则应用usual arithmetic conversions 以获得公共类型。在longdouble 的情况下,常见的类型是double。这是相同类型的计算,如果你例如尝试使用+ 添加两种不同的算术类型,因此命名为通常的算术转换

因此,如果ab 具有相同的类型,则decltype(a&lt;b ? a:b) 将是引用类型,否则将不是引用类型。

这就是函数编译的原因。通用类型总是使得两种输入类型都可以转换为它。这也是为什么函数在类型相等时具有未定义行为的原因,因为 decltype 提供了一个引用,因此您将返回对函数参数之一的引用。

decltype(a&lt;b ? a:a) 不适用于不同的类型,因为如上所述,aa 的公共类型是a 类型的引用。如果b 具有不同的不相关类型,则a &gt; b ? a : b 的结果将是无法绑定到左值引用的纯右值。

【讨论】:

  • 但是如果它不是引用类型,我们仍然会在 double 到 long 转换的情况下收到警告,因为结果应该是整数,而我们在输出中看到 double 结果,这意味着引用已通过或可能是因为通用类型?
  • 而且也不是很清楚什么是常见的类型。您能否也提供一些有关此的更多详细信息?
  • @user2420079 完整(相当复杂)的详细信息在我在问题中提供的链接中。对于两种不同的算术类型,例如longdouble,应用通常的算术转换 以获得通用类型,请参阅en.cppreference.com/w/cpp/language/…。特别是longdouble 的常见类型是doublelong 参数如果以较大的值返回,则将转换为 double
【解决方案2】:

你可能认为

template <class T, class U>
auto x(T a, U b) -> decltype(a < b ? a : b) {
    return a > b ? a : b;
}

std::cout << x<long, double>(1, 2.01) << std::endl;
std::cout << x<long, double>(5, 2.01) << std::endl;

不会编译,因为尾随返回类型decltype(a &lt; b ? a : b),即内部表达式a &lt; b ? a : b 与返回表达式a &gt; b ? a : b 不同,但这是完全合法的,因为返回类型变为-&gt; 之后出现的任何类型.

现在,为什么下面的代码没有编译成功?

template <class T, class U>
auto x(T a, U b) -> decltype(a < b ? a : a) {
    return a > b ? a : b;
}

std::cout << x<long, double>(1, 2.01) << std::endl;
std::cout << x<long, double>(5, 2.01) << std::endl;

这是因为你需要从尾随返回类型中删除引用,像这样:

#include <type_traits>

template <class T, class U>
auto x(T a, U b) -> typename std::remove_reference<decltype(a < b ? a : a)>::type {
    return (a > b ? a : b);
}

现在它编译成功了。检查here

第二个示例没有std::remove_reference 无法编译的原因是因为属于三元运算符的两种类型都是glvalues 和here 据说:

4) 如果 E2 和 E3 是相同类型和相同值的泛左值 类别,则结果具有相同的类型和值类别

所以a &lt; b ? a : a 的结果是glvalue。这就是为什么您需要删除引用。 a &lt; b ? a : b 的结果是 prvalue,因此您不需要删除引用。

【讨论】:

  • 问题是,在第一种情况下,我们遇到了完全相同的问题,因为它返回的是 U& 而不是 T,但它仍然可以编译。
猜你喜欢
  • 2017-03-16
  • 2020-09-13
  • 2016-09-26
  • 1970-01-01
  • 2019-02-05
  • 2013-05-15
  • 1970-01-01
  • 2015-09-10
  • 1970-01-01
相关资源
最近更新 更多