【问题标题】:Why do these two code snippets have the same effect?为什么这两个代码片段具有相同的效果?
【发布时间】:2019-09-25 07:33:10
【问题描述】:
template <typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(b<a?a:b);
template <typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(true?a:b);

我不明白为什么这两个代码sn-ps可以有同样的效果。请给我一些提示和基本解释。

干杯。

【问题讨论】:

  • 您能详细说明您不清楚的地方吗?您是否期望三元运算符中的条件导致两个decltype 表达式产生不同的类型?

标签: c++ c++17 decltype ternary


【解决方案1】:

因为三元运算符返回的类型是根据第二个和第三个参数的类型决定的,而不是根据第一个参数的值。

您可以使用以下代码验证这一点

#include <type_traits>

int main ()
 {
   auto x = true ? 1 : 2l;

   static_assert( std::is_same<decltype(x), long>::value, "!" );
 }

true ? 1 : 2l 永远返回1 并不重要;三元运算符返回1 (int) 和2l (long) 之间的公共类型。那是long

换句话说:没有(目前)constexpr 三元运算符。

【讨论】:

  • 是的。但我想我只是不明白 true 的实际含义吗? a : b, true 是一个选择的条件,它不会改变,对吧?
  • 这就是为什么 decltype(true ? a : b) 总是会返回 a 的类型,对吧?
  • @Edee - 否。true ? a : b 将始终返回 a;但返回的类型也取决于b。编译器不能(根据 C++ 语言规则)做出以下推理:“true 始终是 true,所以我总是返回 a,所以我返回 a 的类型”。编译器必须总是返回ab 之间的公共类型(如果条件的值在编译时已知,则并不重要)。这与ifif constexpr 的问题相同。没有constexpr 三元运算符。
  • 是的,我知道它是一种类型。所以返回类型更多的是基于我的实现,对吧?如果我想要更大的值,那么decltype(true ? a : b)永远是a的类型,但是a的内容会根据我的功能实现而变化,对吧?
  • @Edee 不,你一直这么说,这是完全错误的。表达式 nevertype 取决于它的 value
【解决方案2】:

条件表达式的类型不取决于条件是否为真。

decltype(b&lt;a?a:b) 是表达式b&lt;a?a:b 的类型,始终相同。
它不是decltype(a)decltype(b),具体取决于b&lt;a 的值。

请注意,您提供给decltype 的表达式永远不会被计算 - 仅确定其类型,并且在编译时确定。

有点非正式:

  • 如果a可以转换为b的类型,则表达式与b的类型相同
  • 如果b可以转换为a的类型,则表达式与a的类型相同
  • 否则,表达式类型错误。

(还有一大堆关于标准转换和 yada-yada 的细节,但这是它的要点。)

例如,这不会编译,因为没有有效的转换可以给notgood 一个类型:

int main()
{
     decltype(true ? "hello" : 123.4) notgood;
}

虽然这将编译、运行和定义良好,因为从不评估无效的取消引用:

int main()
{
    decltype(*(int*)0 + 1)` x = 0;
    return x;
}

【讨论】:

  • 我想如果我写的是真的? a : b ,那么 decltype(true ? a : b) 将永远是 a 的类型,但实际上不是,这是我不明白的一点。
  • 除了这些答案中提供的详细信息之外,很难进一步解释。最重要的一点是条件表达式的类型不依赖于任何涉及的
【解决方案3】:

判断条件表达式类型的规则见here

正如其他人已经说过的,关键是要意识到表达方式

E1 ? E2 : E3

作为一个整体是一个表达式,一个表达式有一个在编译时确定的单一类型(和值类别)。它不能根据所采用的路径更改类型,因为通常直到运行时才知道。

因此,规则非常广泛。跳过void 和位域的特殊情况,它的工作原理如下:

  1. 如果 E2 或 E3 的类型为 void ...假设它们没有。
  2. 否则,如果 E2 或 E3 是左值位域 ...假设它们不是。
  3. 否则,如果 E2 和 E3 有不同的类型,至少其中一个是(可能是 cv 限定的)类类型 ...

    好的,这可能是真的。我们还不知道 E1 和 E2 的类型,但这肯定是有道理的。

    如果这种情况适用,则它必须遵循一整套步骤,如果成功,那么它会计算出如何将 E1 隐式转换为 E2,或将 E2 转换为 E1。不管怎样,我们在下一步中选择两个相同类型的子表达式。

  4. 如果E2和E3是相同类型和相同值类别的glvalues,则结果具有相同的类型和值类别

    也就是说,如果我们原来的 T1 和 T2 相同,那么表达式的类型就是这样。这是最简单的情况。

    如果它们是不同的类型,但编译器在上面的第 3 步中发现了隐式转换,我们将查看 (T1,T1)(T2,T2),这同样适用。

  5. 否则,结果是纯右值 [大致 - 匿名临时]。 如果 E2 和 E3 没有相同的类型,并且都具有(可能是 cv 限定的)类类型,则使用下面的内置候选执行重载决议,以尝试将操作数转换为内置类型。 . 转换后的操作数用于代替第 6 步的原始操作数

    也许它们是带有 operator bool 之类的转换运算符的类 - 那么我们还没有找到其他答案,所以我们将转换为 bool 并继续。

  6. 左值到右值、数组到指针和函数到指针的转换应用于第二个和第三个操作数

    这些是一堆标准的隐式转换,只是为了使双方尽可能相似。

    那么,

    1. 如果 E2 和 E3 现在具有相同的类型,则结果是该类型的纯右值

      我们成功地按摩了两边的类型,万岁!

    2. 如果 E2 和 E3 都具有算术或枚举类型:应用usual arithmetic conversions 将它们带入公共类型,该类型就是结果

      通常的算术转换允许你添加,比如说,int 和一个double 并得到一些结果。这将以相同的方式工作。

    3. 等。等等。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-06
    • 1970-01-01
    • 2021-12-17
    • 1970-01-01
    • 1970-01-01
    • 2014-02-02
    • 1970-01-01
    • 2021-10-06
    相关资源
    最近更新 更多