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

Why does this C-style cast not consider static_cast followed by const_cast?

考虑:

float const& f = 5.9e-44f;
int const i = (int&) f;

根据 expr.cast/4 这应该被认为是,按顺序:

  • a const_­cast,
  • a static_­cast,
  • a static_­cast followed by a const_­cast,
  • a reinterpret_­cast, or
  • a reinterpret_­cast followed by a const_­cast,

很明显,static_­cast<int const&> 后跟 const_­cast<int&>viable,结果是 int,值为 0。但是所有的编译器都将 i 初始化为 42,这表明它们采用了 reinterpret_­cast<int const&> 的最后一个选项,然后是 const_­cast<int&>。为什么?

相关:, , , Type punning with (float&)int works, (float const&)int converts like (float)int instead?

这可能是未定义的行为。但是,据我所知,试着回答这个问题:

你施放了 const,然后 reinterpret_cast 它作为 int& (**)
这不是 static_cast?
它已经是对不是 pointer-interconvertibleint& 的左值的引用。 (*)

reinterpret_cast(?) 的结果将是未定义的行为;它会违反 strict aliasing rule.

您可以在尝试之前使用 std::is_pointer_interconvertible_base_of_v<> 检查一下。参见:cppreference.com

如果我们忽略 const 仍然没有 有意义。
我读得越多,我对任何事情就越不确定。这就是为什么我们告诉您不要使用 C 风格的转换。

注释 (*): 错了,是吗?不止一种方法可以为这个演员表蒙皮……
(**): 不是那个……我不知道我在那里说什么……

tl;博士:

  • const_cast<int&>(static_cast<int const&>(f)) 有效 c++
  • (int&)f 应该有相同的结果
  • 但这不是因为一个从未修复过的古老编译器错误

详细说明

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

1.1 static_cast

让我们从 static_cast<int const&>(f):

开始
  • 让我们检查一下转换的结果是什么:
    7.6.1.9 Static cast(强调我的)

    (1) The result of the expression static_­cast<T>(v) is the result of converting the expression v to type T. If T is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue. The static_­cast operator shall not cast away constness (expr.const.cast).

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

  • 然后让我们看看实际发生了什么转换:
    7.6.1.9 Static cast

    (4) An expression E can be explicitly converted to a type T if there is an implicit conversion sequence (over.best.ics) from E to T, [...].
    If T is a reference type, the effect is the same as performing the declaration and initialization
    T t(E);
    for some invented temporary variable t ([dcl.init]) and then using the temporary variable as the result of the conversion.

    • 在我们的例子中,声明如下所示:
      const int& t(f);
    • 为了简短起见,我不会在这里详细说明整个转换过程,您可以在 12.2.4.2 Implicit conversion sequences
    • 中阅读确切的详细信息
    • 在我们的例子中,转换序列将包含 2 个步骤:
      • 将glvalue float转换为prvalue(这也让我们摆脱了const
        7.3.2 Lvalue-to-rvalue conversion(强调我的)

        (1) A glvalue of a non-function, non-array type T can be converted to a prvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.

        鉴于 float 是非 class 类型,这允许我们将 ffloat const& 转换为 float&&

      • 从 float 转换为 int
        7.3.11 Floating-integral conversions

        (1) A prvalue of a floating-point type can be converted to a prvalue of an integer type. The conversion truncates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type.

        所以我们最终从 f.

        得到一个很好转换的 int
  • 所以static_cast<>部分的最终结果是左值int const&.

1.2 const_cast

现在我们知道 static_cast<> 部分是什么 returns,我们可以关注 const_cast<int&>():

  • 结果类型需要为:
    7.6.1.11 Const cast(强调我的)

    (1) The result of the expression const_­cast<T>(v) is of type T. If T is an lvalue reference to object type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions are performed on the expression v. Conversions that can be performed explicitly using const_­cast are listed below. No other conversion shall be performed explicitly using const_­cast.

    static_cast<> 产生了一个左值,因此 const_cast<> 的结果也必须是一个左值。

  • const_cast<>是做什么转换的? 7.6.1.11 Const cast(强调我的)

    (4) For two object types T1 and T2, if a pointer to T1 can be explicitly converted to the type “pointer to T2” using a const_­cast, then the following conversions can also be made:
    (4.1) an lvalue of type T1 can be explicitly converted to an lvalue of type T2 using the cast const_­cast<T2&>;
    (4.2) a glvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_­cast<T2&&>; and
    (4.3) if T1 is a class type, a prvalue of type T1 can be explicitly converted to an xvalue of type T2 using the cast const_­cast<T2&&>.

    The result of a reference const_­cast refers to the original object if the operand is a glvalue and to the result of applying the temporary materialization conversion otherwise.

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

1.3结论

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

您甚至可以根据 6.7.7 Temporary objects

延长引用的生命周期

(6) The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following:
[...]
- (6.6) a
- (6.6.1) const_cast (expr.const.cast),
[...]
converting, without a user-defined conversion, a glvalue operand that is one of these expressions to a glvalue that refers to the object designated by the operand, or to its complete object or a subobject thereof,
[...]

所以这也是合法的:

float const& f = 1.2f; 
int& i = const_cast<int&>(static_cast<int const&>(f));

i++; // legal
return i; // legal, result: 2
1.4 注释
  • 在这种情况下,static_cast<> 的操作数是一个 const float 引用是无关紧要的,因为允许 static_cast 执行的左值到右值转换可以剥离 const。
    所以这些也是合法的:
    int& i = const_cast<int&>(static_cast<int const&>(1.0f));
    // when converting to rvalue you don't even need a const_cast:
    // (due to 7.6.1.9 (4), because int&& t(1.0f); is well-formed)
    // the result of the static_cast would be an xvalue in this case. 
    int&& ii = static_cast<int&&>(1.0f);
    
  • 因此,以下 C 风格的转换也是合式的:
    float f = 1.2f;
    int const& i = (int const&)f; // legal, will use static_cast
    int&& ii = (int&&)f; // legal, will use static_cast
    

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

你在技术上是正确的,它应该可以工作,因为允许 c 风格的转换执行这个转换序列:

7.6.3 Explicit type conversion (cast notation)

(4) The conversions performed by
(4.1) a const_­cast (expr.const.cast),
(4.2) a static_­cast (expr.static.cast),
(4.3) a static_­cast followed by a const_­cast,
(4.4) a reinterpret_­cast (expr.reinterpret.cast), or
(4.5) a reinterpret_­cast followed by a const_­cast,
can be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply, [...].

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

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

2.1 甚至是一个open-std.org issue (#909):

According to 7.6.3 [expr.cast] paragraph 4, one possible interpretation of an old-style cast is as a static_cast followed by a const_cast. One would therefore expect that the expressions marked #1 and #2 in the following example would have the same validity and meaning:

struct S {
  operator const int* ();
};

void f(S& s)  {
  const_cast<int*>(static_cast<const int*>(s));  // #1
  (int*) s;  // #2
}

However, a number of implementations issue an error on #2.

Is the intent that (T*)x should be interpreted as something like const_cast<T*>(static_cast<const volatile T*>(x))

结果是:

Rationale (July, 2009): According to the straightforward interpretation of the wording, the example should work. This appears to be just a compiler bug.

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

2.2 编译器错误票

gcc 和 clang 已经有关于此问题的公开错误:

2.3 为什么这么多年了还没有修复?

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

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

If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.

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

struct B { int i; };
struct D : B {};

D d;
d.i = 12;
B const& ref = d;

// works
D& k = (D&)ref;

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

2.4 gcc代码分析

在 gcc 的情况下,c 样式转换当前通过 cp_build_c_cast:

解决
tree cp_build_c_cast(location_t loc, tree type, tree expr, tsubst_flags_t complain) {
  tree value = expr;
  tree result;
  bool valid_p;
  // [...]
  /* A C-style cast can be a const_cast.  */
  result = build_const_cast_1 (loc, type, value, complain & tf_warning,
                   &valid_p);
  if (valid_p)
    {
      if (result != error_mark_node)
    {
      maybe_warn_about_useless_cast (loc, type, value, complain);
      maybe_warn_about_cast_ignoring_quals (loc, type, complain);
    }
      return result;
    }

  /* Or a static cast.  */
  result = build_static_cast_1 (loc, type, value, /*c_cast_p=*/true,
                &valid_p, complain);
  /* Or a reinterpret_cast.  */
  if (!valid_p)
    result = build_reinterpret_cast_1 (loc, type, value, /*c_cast_p=*/true,
                       &valid_p, complain);
  /* The static_cast or reinterpret_cast may be followed by a
     const_cast.  */
  if (valid_p
      /* A valid cast may result in errors if, for example, a
     conversion to an ambiguous base class is required.  */
      && !error_operand_p (result))
  {
    tree result_type;

    maybe_warn_about_useless_cast (loc, type, value, complain);
    maybe_warn_about_cast_ignoring_quals (loc, type, complain);

    /* Non-class rvalues always have cv-unqualified type.  */
    if (!CLASS_TYPE_P (type))
      type = TYPE_MAIN_VARIANT (type);
    result_type = TREE_TYPE (result);

    if (!CLASS_TYPE_P (result_type) && !TYPE_REF_P (type))
      result_type = TYPE_MAIN_VARIANT (result_type);

    /* If the type of RESULT does not match TYPE, perform a
      const_cast to make it match.  If the static_cast or
      reinterpret_cast succeeded, we will differ by at most
      cv-qualification, so the follow-on const_cast is guaranteed
      to succeed.  */
    if (!same_type_p (non_reference (type), non_reference (result_type)))
    {
      result = build_const_cast_1 (loc, type, result, false, &valid_p);
      gcc_assert (valid_p);
    }

    return result;
  }

  return error_mark_node;
}

实现基本上是:

  • 试试const_cast
  • 尝试 static_cast(暂时忽略潜在的 const 不匹配)
  • 尝试 reinterpret_cast(同时暂时忽略潜在的 const 不匹配)
  • 如果 static_castreinterpret_cast 变体中存在 const 不匹配,请在其前面打一个 const_cast

所以出于某种原因 build_static_cast_1 在这种情况下没有成功,所以 build_reinterpret_cast_1 开始做这件事(由于严格的别名规则,这将导致未定义的行为)