为什么这个 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-interconvertible 到 int&
的左值的引用。 (*)
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
应该有相同的结果
- 但这不是因为一个从未修复过的古老编译器错误
- open std issue 909
- gcc bug(已确认,但从未修复)
- clang bug
详细说明
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 类型,这允许我们将 f
从 float 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 已经有关于此问题的公开错误:
- gcc: Bug 77465 (C++DR909) - [DR909] rejected C-style cast involving casting away constness from result of conversion operator
- 叮当声:Bug 30266 - [CWG 909] C-style cast necessitating static_cast -> const_cast fails for conversion operator
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_cast
或 reinterpret_cast
变体中存在 const 不匹配,请在其前面打一个 const_cast
。
所以出于某种原因 build_static_cast_1
在这种情况下没有成功,所以 build_reinterpret_cast_1
开始做这件事(由于严格的别名规则,这将导致未定义的行为)
考虑:
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 aconst_cast
,- a
reinterpret_cast
, or- a
reinterpret_cast
followed by aconst_cast
,
很明显,static_cast<int const&>
后跟 const_cast<int&>
是 viable,结果是 int
,值为 0。但是所有的编译器都将 i
初始化为 42,这表明它们采用了 reinterpret_cast<int const&>
的最后一个选项,然后是 const_cast<int&>
。为什么?
相关:
这可能是未定义的行为。但是,据我所知,试着回答这个问题:
你施放了 (**)const
,然后 reinterpret_cast
它作为 int&
。
这不是 static_cast
?
它已经是对不是 pointer-interconvertible 到 int&
的左值的引用。 (*)
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
应该有相同的结果- 但这不是因为一个从未修复过的古老编译器错误
- open std issue 909
- gcc bug(已确认,但从未修复)
- clang bug
详细说明
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 expressionv
to typeT
. IfT
is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; ifT
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 typeT
if there is an implicit conversion sequence (over.best.ics) fromE
toT
, [...].
IfT
is a reference type, the effect is the same as performing the declaration and initialization
T t(E);
for some invented temporary variablet
([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. IfT
is an incomplete type, a program that necessitates this conversion is ill-formed. IfT
is a non-class type, the type of the prvalue is the cv-unqualified version ofT
. Otherwise, the type of the prvalue isT
.鉴于
float
是非 class 类型,这允许我们将f
从float 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 typeT
. IfT
is an lvalue reference to object type, the result is an lvalue; ifT
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 expressionv
. 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
andT2
, if a pointer toT1
can be explicitly converted to the type “pointer toT2
” using a const_cast, then the following conversions can also be made:
(4.1) an lvalue of typeT1
can be explicitly converted to an lvalue of typeT2
using the castconst_cast<T2&>
;
(4.2) a glvalue of typeT1
can be explicitly converted to an xvalue of typeT2
using the castconst_cast<T2&&>
; and
(4.3) ifT1
is a class type, a prvalue of typeT1
can be explicitly converted to an xvalue of typeT2
using the castconst_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) aconst_cast
(expr.const.cast),
(4.2) astatic_cast
(expr.static.cast),
(4.3) astatic_cast
followed by aconst_cast
,
(4.4) areinterpret_cast
(expr.reinterpret.cast), or
(4.5) areinterpret_cast
followed by aconst_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 likeconst_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 已经有关于此问题的公开错误:
- gcc: Bug 77465 (C++DR909) - [DR909] rejected C-style cast involving casting away constness from result of conversion operator
- 叮当声:Bug 30266 - [CWG 909] C-style cast necessitating static_cast -> const_cast fails for conversion operator
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_cast
或reinterpret_cast
变体中存在 const 不匹配,请在其前面打一个const_cast
。
所以出于某种原因 build_static_cast_1
在这种情况下没有成功,所以 build_reinterpret_cast_1
开始做这件事(由于严格的别名规则,这将导致未定义的行为)