Consider:
float const& f = 5.9e-44f;
int const i = (int&) f;
Per expr.cast/4 this should be considered as, in order:
- a
const_cast
,- a
static_cast
,- a
static_cast
followed by aconst_cast
,- a
reinterpret_cast
, or- a
reinterpret_cast
followed by aconst_cast
,
Clearly a static_cast<int const&>
followed by a const_cast<int&>
is viable and will result in an int
with value 0. But all compilers instead initialize i
to 42, indicating that they took the last option of reinterpret_cast<int const&>
followed by const_cast<int&>
. Why?
Related: In C++, can a C-style cast invoke a conversion function and then cast away constness?, Why is (int&)0 ill-formed?, Does the C++ specification say how types are chosen in the static_cast/const_cast chain to be used in a C-style cast?, Type punning with (float&)int works, (float const&)int converts like (float)int instead?
Static casts are only available in C++.
In short: static_cast<>() gives you a compile time checking ability, C-Style cast doesn't. static_cast<>() is more readable and can be spotted easily anywhere inside a C++ source code, C_Style cast is'nt. Intentions are conveyed much better using C++ casts.
C-style casts can be used to convert any type into any other type, potentially with unsafe results (such as casting an integer into a pointer type). (<type>)<value> This example casts an int to a double for the purpose of avoiding truncation due to integer division: double result = (double)4/5; Popular pages.
static_cast − This is used for the normal/ordinary type conversion. This is also the cast responsible for implicit type coersion and can also be called explicitly. You should use it in cases like converting float to int, char to int, etc. dynamic_cast −This cast is used for handling polymorphism.
const_cast<int&>(static_cast<int const&>(f))
is valid c++(int&)f
should have the same resultconst_cast<int&>(static_cast<int const&>(f))
worksstatic_cast
Let's start with the static_cast<int const&>(f)
:
Let's check what the result of that cast would be:
7.6.1.9 Static cast (emphasis mine)
(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&
is an lvalue reference type, so the result of the static_cast<>()
must be some sort of lvalue.
Then let's find out what conversion actually happens:
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 initializationT 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);
convert the glvalue float to a prvalue (this also allows us to get rid of const
)
7.3.2 Lvalue-to-rvalue conversion (emphasis mine)
(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
.
Given that float
is of non-class type, this allows us to convert f
from float const&
to float&&
.
convert from float to 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.
So we end up with a nicely converted int
value from f
.
So the final result of the static_cast<>
part is an lvalue int const&
.
const_cast
Now that we know what the static_cast<>
part returns, we can focus on the const_cast<int&>()
:
The result type needs to be:
7.6.1.11 Const cast (emphasis mine)
(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.
The static_cast<>
resulted in an lvalue, so the result of the const_cast<>
must also be an lvalue.
What conversion does the const_cast<>
do?
7.6.1.11 Const cast (emphasis mine)
(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.
So the const_cast<>
will convert the lvalue const int&
to an int&
lvalue, which will refer to the same object.
const_cast<int&>(static_cast<int const&>(f))
is well-formed and will result in a lvalue int reference.
You can even extend the lifetime of the reference as per 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,
[...]
So this would also be legal:
float const& f = 1.2f;
int& i = const_cast<int&>(static_cast<int const&>(f));
i++; // legal
return i; // legal, result: 2
static_cast<>
is a const float reference, since the lvalue-to-rvalue conversion that static_cast is allowed to perform can strip away 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);
float f = 1.2f;
int const& i = (int const&)f; // legal, will use static_cast
int&& ii = (int&&)f; // legal, will use static_cast
(int&)f
doesn't workYou're technically correct in that it should work, because a c-style cast is allowed to perform this conversion sequence:
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, [...].
So const_cast<int&>(static_cast<int const&>(f))
should definitely be a valid conversion sequence.
The reason why this doesn't work is actually a very, very old compiler bug.
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))
The resultion was:
Rationale (July, 2009): According to the straightforward interpretation of the wording, the example should work. This appears to be just a compiler bug.
So the standard agrees with your conclusion, it's just that no compiler actually implements that interpretation.
There are already open bugs for gcc & clang regarding this issue:
I don't know, but given they have to implement a new standard roughly every 3 years now with tons of changes to the language every time it seems reasonable to ignore issues that most programmers probably won't ever encounter.
Note that this is only a problem for primitive types. My guess is that the reason for the bug is that for those the cv-qualifiers can be dropped by a static_cast
/ reinterpret_cast
due to the lvalue-to-rvalue conversion rule.
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.
Note that this bug only affects non-class types, for class-types it'll work perfectly:
struct B { int i; };
struct D : B {};
D d;
d.i = 12;
B const& ref = d;
// works
D& k = (D&)ref;
There will always be a few edge-cases that are not properly implemented in each & every compiler, if it bothers you you can provide a fix & maybe they'll merge it with the next version (at least for clang & gcc).
In the case of gcc a c-style cast currently gets resolved by 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;
}
The implementation is basically:
const_cast
static_cast
(while temporarily ignoring potential const mismatches)reinterpret_cast
(while temporarily ignoring potential const mismatches)static_cast
or reinterpret_cast
variant, slap a const_cast
in front of it.So for some reason build_static_cast_1
doesn't succeed in this case, so build_reinterpret_cast_1
gets to do it's thing (which will result in undefined behaviour due to the strict aliasing rule)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With