r值参考铸造和临时物化

时间:2018-02-14 21:51:57

标签: c++ c++11 language-lawyer

以下代码的输出产生:

void doit(const T1 &, const T2 &) [T1 = unsigned long, T2 = int]
t1 == t2
t1 == (T1)t2
t1 != (T1&)t2
t1 == (T1&&)t2

我知道t1 == t2案例只是一个不可或缺的推广。

第二种情况t1 == (T1)t2是一样的,只是明确的。

第三种情况t1 == (T1&)t2必须是某种reinterpret_cast ......但是,进一步的解释会有所帮助。

第四个案例t1 == (T1&&)t2就是我所坚持的。我在问题标题中加入了“临时实现”一词,因为这是我能得到的最接近某种答案。

有人可以查看这四个案例吗?

代码:

#include <iostream>    

template <typename T1, typename T2>
void doit(const T1& t1, const T2& t2) {
  std::cout << __PRETTY_FUNCTION__ << '\n';

  if (t1 == t2) {
    std::cout << "t1 == t2" << '\n';
  }
  else {
    std::cout << "t1 != t2" << '\n';
  }    

  if (t1 == (T1)t2) {
    std::cout << "t1 == (T1)t2" << '\n';
  }
  else {
    std::cout << "t1 != (T1)t2" << '\n';
  }    

  if (t1 == (T1&)t2) {
    std::cout << "t1 == (T1&)t2" << '\n';
  }
  else {
    std::cout << "t1 != (T1&)t2" << '\n';
  }    

  if (t1 == (T1&&)t2) {
    std::cout << "t1 == (T1&&)t2" << '\n';
  }
  else {
    std::cout << "t1 != (T1&&)t2" << '\n';
  }
}    

int main() {
  const unsigned long a = 1;
  const int b = 1;    

  doit(a, b);    

  return 0;
}

2 个答案:

答案 0 :(得分:1)

编译器尝试按以下顺序将c样式转换解释为c ++样式转换(有关完整详细信息,请参阅cppreference):

  1. 的const_cast
  2. 的static_cast
  3. static_cast后跟const_cast
  4. 的reinterpret_cast
  5. reinterpret_cast后跟const_cast
  6. (T1)t2的解释非常简单。 const_cast失败,但static_cast有效,因此它被解释为static_cast<T1>(t2)(上面的#2)。

    对于(T1&)t2,无法通过int&unsigned long&转换为static_castconst_caststatic_cast都失败,因此reinterpret_cast最终会被使用,从而reinterpret_cast<T1&>(t2)。确切地说,#5是上面的,因为t2是const:const_cast<T1&>(reinterpret_cast<const T1&>(t2))

    编辑:static_cast的{​​{1}}由于cppreference中的关键行而失败:&#34;如果演员阵容可以多种方式解释为static_cast然后是const_cast,它无法编译。&#34; 。涉及隐式转换,以下所有内容都是有效的(我假设至少存在以下重载):

    • (T1&)t2
    • T1 c1 = t2; const_cast<T1&>(static_cast<const T1&>(c1))
    • const T1& c1 = t2; const_cast<T1&>(static_cast<const T1&>(c1))

    请注意,实际表达式T1&& c1 = t2; const_cast<T1&>(static_cast<const T1&>(std::move(c1)))会导致未定义的行为,正如Swift指出的那样(假设为t1 == (T1&)t2)。包含sizeof(int) != sizeof(unsigned long)的地址正在被处理(重新解释)为持有int。在unsigned long中交换ab的定义顺序,结果将更改为相等(在x86系统上使用gcc)。由于错误的main(),这是唯一具有未定义行为的情况。其他案例定义明确,结果是特定于平台的。

    对于reinterpret_cast,转化是从(T1&&)t2int (lvalue)unsigned long (xvalue)基本上是xvalue,可以移动;&#34;它不是一个参考。转换为lvalue(上面的#2)。转化率相当于static_cast<T1&&>(t2)std::move((T1)t2)。编写代码时,请使用std:move(static_cast<T1>(t2))代替std:move(static_cast<T1>(t2)),因为意图要清晰得多。

    此示例说明了为什么应该使用c ++样式转换而不是c样式转换。使用c ++样式转换时代码意图很明显,因为开发人员明确指定了正确的转换。使用c样式转换,编译器会选择实际转换,并可能导致令人惊讶的结果。

答案 1 :(得分:-2)

从技术上讲,所有四种变体都是依赖于平台的

t1 == t2

t2升级为“long”,转换为unsigned long

t1 == (T1)t2

signed int会转换为unsigned long的表示形式,然后先将其转换为long。如果必须将UB视为UB,则存在争议,因为结果取决于平台,我真的不确定它现在是如何定义的,除了C99标准中的条款。比较结果与t1 == t2相同。

t1 == (T1&)t2

强制转换结果是一个引用,您将引用(实际上是指针)与T1进行比较,该操作会产生未知结果。为什么?引用js指向不同的类型。

t1 == (T1&&)t2

强制转换表达式产生一个右值,因此你比较值,但它的类型是右值引用。比较结果与t1 == t2相同。

PS。 如果您使用这样的值,有趣的事情发生(取决于平台,基本上是UB):

  const unsigned long a = 0xFFFFFFFFFFFFFFFF;
  const int b = -1;  

输出可能是:

t1 == t2
t1 == (T1)t2
t1 == (T1&)t2  
t1 == (T1&&)t2

这是为什么在将无符号值与有符号值进行比较时要小心的原因之一,尤其是&lt;或者&gt;。 -1会大于1,你会感到惊讶。

如果unsigned long是64位int并且指针等于64位int。 -1如果转换为unsigned,则成为其二进制表示。本质上,b将成为地址为0xFFFFFFFFFFFFFFFF的指针。