为什么这个 C 风格的转换不考虑 static_cast 后跟 const_cast?

eca*_*mur 11 c++ casting language-lawyer reinterpret-cast

考虑:

float const& f = 5.9e-44f;
int const i = (int&) f;
Run Code Online (Sandbox Code Playgroud)

根据expr.cast/4这应该被视为,为了:

  • const_­cast
  • static_­cast
  • astatic_­cast后跟 a const_­cast,
  • reinterpret_­cast,或
  • areinterpret_­cast后跟 a const_­cast,

显然 astatic_­cast<int const&>后跟 aconst_­cast<int&>可行的,并且将导致int值为0 的 a。但是所有编译器都初始化i42,表明它们采用了最后一个选项reinterpret_­cast<int const&>后跟const_­cast<int&>。为什么?

相关:在 C++ 中,C 风格的强制转换可以调用转换函数然后抛弃常量吗?,为什么 (int&)0 格式错误?, C++ 规范是否说明了如何在 static_cast/const_cast 链中选择类型以用于 C 样式强制转换?,用 (float&)int 输入双关语有效,(float const&)int 转换为 (float)int 吗?

Tur*_*ght 6

长话短说:

\n
    \n
  • const_cast<int&>(static_cast<int const&>(f))是有效的 c++
  • \n
  • (int&)f应该有相同的结果
  • \n
  • 但这并不是由于一个古老的编译器错误从未得到修复\n\n
  • \n
\n
\n

长解释

\n

1. 为什么const_cast<int&>(static_cast<int const&>(f))有效

\n
1.1 的static_cast
\n

让我们从以下开始static_cast<int const&>(f)

\n
    \n
  • 让我们检查一下该转换的结果是什么:
    \n 7.6.1.9 静态转换(强调我的)

    \n
    \n

    (1)表达式的结果static_\xc2\xadcast<T>(v)是将表达式转换v为类型的结果T。如果T左值引用类型或函数类型的右值引用,则结果是左值;如果T是对象类型的右值引用,则结果是 xvalue;否则,结果是纯右值。static_\xc2\xadcast 运算符不应放弃常量性 ( expr.const.cast )。

    \n
    \n

    int const&是左值引用类型,因此 的结果static_cast<>()必须是某种左值。

    \n
  • \n
  • 然后让我们看看实际发生了什么转换:
    \n 7.6.1.9 静态转换

    \n
    \n

    (4)如果存在从to到 的隐式转换序列 ( over.best.ics ) ,则表达式E可以显式转换为类型,[...]。\n如果是引用类型,则效果与对某些发明的临时变量([dcl.init])执行声明和初始化,然后使用该临时变量作为转换结果相同。TET
    T
    T t(E);
    t

    \n
    \n
      \n
    • 在我们的例子中,声明将如下所示:
      \nconst int& t(f);
    • \n
    • 为了简短起见,我不打算在这里详细说明整个转换过程,您可以在12.2.4.2 隐式转换序列中阅读确切的详细信息
    • \n
    • 在我们的例子中,转换序列将包含 2 个步骤:\n
        \n
      • 将左值浮点数转换为纯右值(这也允许我们摆脱const
        \n 7.3.2 左值到右值的转换(强调我的)

        \n
        \n

        (1)非函数、非数组类型的左值T可以转换为纯右值。如果T是不完整类型,则需要此转换的程序格式错误。如果T是非类类型,则纯右值的类型是的 cv 未限定版本T。否则,纯右值的类型为T

        \n
        \n

        鉴于它float是非类类型,这允许我们f从转换float const&float&&

        \n
      • \n
      • 从 float 转换为 int
        \n 7.3.11 浮点积分转换

        \n
        \n

        (1)浮点类型的纯右值可以转换为整数类型的纯右值。转换截断;也就是说,小数部分被丢弃。如果截断值无法在目标类型中表示,则行为未定义。

        \n
        \n

        所以我们最终得到了一个很好的转换intf

        \n
      • \n
      \n
    • \n
    \n
  • \n
  • 所以该部分的最终结果static_cast<>是一个左值int const&

    \n
  • \n
\n
1.2 的const_cast
\n

现在我们知道该static_cast<>部分返回什么,我们可以关注const_cast<int&>()

\n
    \n
  • 结果类型必须是:
    \n 7.6.1.11 Const cast(强调我的)

    \n
    \n

    (1)表达式的结果的const_\xc2\xadcast<T>(v)类型为T。如果T对象类型的左值引用,则结果是左值;如果T是对象类型的右值引用,则结果是 xvalue;否则,结果是纯右值,并且对表达式 执行左值到右值、数组到指针和函数到指针的标准转换v。下面列出了可以使用 const_\xc2\xadcast 显式执行的转换。不得使用 const_\xc2\xadcast 显式执行其他转换。

    \n
    \n

    Thestatic_cast<>结果是一个左值,因此 the 的结果const_cast<>也必须是一个左值。

    \n
  • \n
  • 进行什么转换const_cast<>?\n 7.6.1.11 Const 转换(强调我的)

    \n
    \n

    (4)对于两个对象类型T1T2,如果T1可以使用 const_\xc2\xadcast 将 to 的指针显式转换为 \xe2\x80\x9c 类型的指针到T2\xe2\x80\x9d ,则还可以进行以下转换:
    \n (4.1) 类型的左值 T1 可以 T2 使用强制 const_\xc2\xadcast<T2&>转换显式转换为类型的左值;
    \n (4.2)类型的左值T1可以T2使用强制转换显式转换为类型的x值const_\xc2\xadcast<T2&&>;\
    n (4.3)如果T1是类类型,则可以使用强制转换T1将类型的纯右值显式转换为类型的 xvalue 。\n如果操作数是泛左值,则引用 const_\xc2\xadcast 的结果引用原始对象,否则引用应用临时物化转换的结果。T2const_\xc2\xadcast<T2&&>

    \n
    \n

    因此,const_cast<>会将左值转换const int&int&左值,它将引用同一个对象。

    \n
  • \n
\n
1.3 结论
\n

const_cast<int&>(static_cast<int const&>(f))格式良好,将产生左值 int 引用。

\n

您甚至可以按照6.7.7 临时对象延长引用的生命周期

\n
\n

(6)如果引用所绑定的泛左值是通过以下方式获得的,则引用所绑定的临时对象或作为引用所绑定的子对象的完整对象的临时对象在引用的生命周期内持续存在以下内容:
\n[...]
\n- (6.6) a
\n- (6.6.1) const_cast ( expr.const.cast )、
\n[...]
\n转换,无需用户定义的转换,一个泛左值操作数,它是泛左值的表达式之一,该泛左值引用操作数指定的对象,或其完整对象或其子对象,
\n[...]

\n
\n

所以这也是合法的:

\n
float const& f = 1.2f; \nint& i = const_cast<int&>(static_cast<int const&>(f));\n\ni++; // legal\nreturn i; // legal, result: 2\n
Run Code Online (Sandbox Code Playgroud)\n
1.4 注意事项
\n
    \n
  • 在这种情况下, 的操作数static_cast<>是 const float 引用是无关紧要的,因为允许 static_cast 执行的左值到右值转换可以去除 const。
    \n所以这些也是合法的:\n
    int& i = const_cast<int&>(static_cast<int const&>(1.0f));\n// when converting to rvalue you don\'t even need a const_cast:\n// (due to 7.6.1.9 (4), because int&& t(1.0f); is well-formed)\n// the result of the static_cast would be an xvalue in this case. \nint&& ii = static_cast<int&&>(1.0f);\n
    Run Code Online (Sandbox Code Playgroud)\n
  • \n
  • 因此,以下 c 风格转换也是格式良好的:\n
    float f = 1.2f;\nint const& i = (int const&)f; // legal, will use static_cast\nint&& ii = (int&&)f; // legal, will use static_cast\n
    Run Code Online (Sandbox Code Playgroud)\n
  • \n
\n
\n

2.为什么(int&)f不起作用

\n

您在技术上是正确的,因为它应该可以工作,因为允许 c 样式强制转换来执行此转换序列:

\n

7.6.3 显式类型转换(强制转换表示法)

\n
\n

(4)
由\n (4.1) a const_\xc2\xadcast( expr.const.cast ),
\n (4.2) a static_\xc2\xadcast( expr.static.cast ),
\n (4.3) astatic_\xc2\xadcast后接 a const_\xc2\xadcast,
\n (4.4)执行的转换a reinterpret_\xc2\xadcast( expr.reinterpret.cast ) 或
\n (4.5) areinterpret_\xc2\xadcast后跟 a const_\xc2\xadcast,
\n 可以使用显式类型转换的强制转换表示法来执行。相同的语义限制和行为也适用,[...]。

\n
\n

所以const_cast<int&>(static_cast<int const&>(f))绝对应该是一个有效的转换序列。

\n

这不起作用的原因实际上是一个非常非常古老的编译器错误。

\n
2.1 这甚至是一个open-std.org 问题 (#909)
\n
\n

根据 7.6.3 [expr.cast] 第 4 段,旧式强制转换的一种可能解释是 static_cast 后跟 const_cast。因此,人们会期望以下示例中标记为 #1 和 #2 的表达式具有相同的有效性和含义:

\n
struct S {\n  operator const int* ();\n};\n\nvoid f(S& s)  {\n  const_cast<int*>(static_cast<const int*>(s));  // #1\n  (int*) s;  // #2\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然而,许多实现都会在#2 上发出错误。

\n

意图是否(T*)x应该被解释为类似const_cast<T*>(static_cast<const volatile T*>(x))

\n
\n

结果是:

\n
\n

基本原理(2009 年 7 月):\n根据措辞的直接解释,该示例应该可以工作。这似乎只是一个编译器错误。

\n
\n

所以标准同意你的结论,只是没有编译器真正实现这种解释。

\n
2.2 编译器错误单
\n

关于此问题,gcc 和 clang 已经存在未解决的错误:

\n\n
2.3 为什么这么多年了这个问题还没有解决?
\n

我不知道,但考虑到他们现在大约每 3 年就要实施一个新标准,每次都会对语言进行大量更改,忽略大多数程序员可能不会遇到的问题似乎是合理的。

\n

请注意,这只是原始类型的问题。我的猜测是,该错误的原因是,由于左值到右值的转换规则, cv 限定符可以被static_cast/删除。reinterpret_cast

\n
\n

如果 T 是非类类型,则纯右值的类型是T 的cv 未限定版本。否则,纯右值的类型为 T。

\n
\n

请注意,此错误仅影响非类类型,对于类类型它将完美工作:

\n
struct B { int i; };\nstruct D : B {};\n\nD d;\nd.i = 12;\nB const& ref = d;\n\n// works\nD& k = (D&)ref;\n
Run Code Online (Sandbox Code Playgroud)\n

总会有一些边缘情况在每个编译器中没有正确实现,如果它困扰您,您可以提供修复,也许他们会将其与下一个版本合并(至少对于 clang 和 gcc)。

\n
2.4 gcc代码分析
\n

在 gcc 的情况下,c 风格的转换当前通过以下方式解决cp_build_c_cast

\n
tree cp_build_c_cast(location_t loc, tree type, tree expr, tsubst_flags_t complain) {\n  tree value = expr;\n  tree result;\n  bool valid_p;\n  // [...]\n  /* A C-style cast can be a const_cast.  */\n  result = build_const_cast_1 (loc, type, value, complain & tf_warning,\n                   &valid_p);\n  if (valid_p)\n    {\n      if (result != error_mark_node)\n    {\n      maybe_warn_about_useless_cast (loc, type, value, complain);\n      maybe_warn_about_cast_ignoring_quals (loc, type, complain);\n    }\n      return result;\n    }\n\n  /* Or a static cast.  */\n  result = build_static_cast_1 (loc, type, value, /*c_cast_p=*/true,\n                &valid_p, complain);\n  /* Or a reinterpret_cast.  */\n  if (!valid_p)\n    result = build_reinterpret_cast_1 (loc, type, value, /*c_cast_p=*/true,\n                       &valid_p, complain);\n  /* The static_cast or reinterpret_cast may be followed by a\n     const_cast.  */\n  if (valid_p\n      /* A valid cast may result in errors if, for example, a\n     conversion to an ambiguous base class is required.  */\n      && !error_operand_p (result))\n  {\n    tree result_type;\n\n    maybe_warn_about_useless_cast (loc, type, value, complain);\n    maybe_warn_about_cast_ignoring_quals (loc, type, complain);\n\n    /* Non-class rvalues always have cv-unqualified type.  */\n    if (!CLASS_TYPE_P (type))\n      type = TYPE_MAIN_VARIANT (type);\n    result_type = TREE_TYPE (result);\n\n    if (!CLASS_TYPE_P (result_type) && !TYPE_REF_P (type))\n      result_type = TYPE_MAIN_VARIANT (result_type);\n\n    /* If the type of RESULT does not match TYPE, perform a\n      const_cast to make it match.  If the static_cast or\n      reinterpret_cast succeeded, we will differ by at most\n      cv-qualification, so the follow-on const_cast is guaranteed\n      to succeed.  */\n    if (!same_type_p (non_reference (type), non_reference (result_type)))\n    {\n      result = build_const_cast_1 (loc, type, result, false, &valid_p);\n      gcc_assert (valid_p);\n    }\n\n    return result;\n  }\n\n  return error_mark_node;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

实现基本上是:

\n
    \n
  • 尝试一个const_cast
  • \n
  • 尝试static_cast(同时暂时忽略潜在的 const 不匹配)
  • \n
  • 尝试reinterpret_cast(同时暂时忽略潜在的 const 不匹配)
  • \n
  • static_cast如果或变体中存在 const 不匹配reinterpret_cast,请const_cast在其前面加上 a。
  • \n
\n

因此,由于某种原因build_static_cast_1在这种情况下不会成功,因此build_reinterpret_cast_1需要做它的事情(由于严格的别名规则,这将导致未定义的行为)

\n


归档时间:

查看次数:

190 次

最近记录:

4 年,11 月 前