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) 表达式static_cast<T>(v) 的结果是将表达式v 转换为类型T 的结果。如果T 是左值引用类型 或对函数类型的右值引用,则结果为左值;如果T 是对对象类型的右值引用,则结果是一个xvalue;否则,结果为纯右值。 static_cast 运算符不应抛弃常量 (expr.const.cast)。
int const& 是左值引用类型,所以static_cast<>() 的结果必须是某种左值。
-
那么让我们看看实际发生了什么转换:
7.6.1.9 Static cast
(4) 如果存在从E 到T 的隐式转换序列(over.best.ics),则表达式E 可以显式转换为类型T,[ ...]。
如果T是引用类型,效果和声明初始化一样
T t(E);
对于一些发明的临时变量t ([dcl.init]),然后使用临时变量作为转换的结果。
-
所以static_cast<> 部分的最终结果是一个左值int const&。
1.2 const_cast
现在我们知道static_cast<> 部分返回了什么,我们可以专注于const_cast<int&>():
-
结果类型需要是:
7.6.1.11 Const cast(强调我的)
(1) 表达式const_cast<T>(v) 的结果是T 类型。如果T 是对对象类型的左值引用,则结果是左值;如果T 是对对象类型的右值引用,则结果是一个xvalue;否则,结果是纯右值,并且左值到右值、数组到指针和函数到指针的标准转换在表达式v 上执行。下面列出了可以使用 const_cast 显式执行的转换。不应使用 const_cast 显式执行其他转换。
static_cast<> 产生一个左值,因此const_cast<> 的结果也必须是一个左值。
-
const_cast<> 做了什么转换?
7.6.1.11 Const cast(强调我的)
(4) 对于T1和T2这两种对象类型,如果指向T1的指针可以使用const_cast显式转换为“指向T2的指针”类型,那么也可以进行以下转换:
(4.1)左值类型 T1 可以显式转换为左值类型 T2 使用强制转换 const_cast<T2&>;
(4.2)T1 类型的左值可以显式转换为 @987654397 类型的 xvalue @使用演员const_cast<T2&&>;并且
(4.3) 如果T1 是类类型,则T1 类型的纯右值可以使用强制转换const_cast<T2&&> 显式转换为T2 类型的xvalue。
如果操作数是泛左值,则引用 const_cast 的结果引用原始对象,否则引用应用临时实现转换的结果。
所以const_cast<> 会将左值const int& 转换为int& 左值,这将指向同一个对象。
1.3结论
const_cast<int&>(static_cast<int const&>(f)) 格式正确,会产生一个左值 int 引用。
您甚至可以按照6.7.7 Temporary objects 延长引用的生命周期
(6) 引用绑定的临时对象或作为引用绑定的子对象的完整对象的临时对象在引用的生命周期内持续存在,如果glvalue通过以下方式之一获得引用所绑定的:
[...]
- (6.6) 一个
- (6.6.1)const_cast (expr.const.cast),
[...]
在没有用户定义的转换的情况下,将作为这些表达式之一的泛左值操作数转换为指代由操作数指定的对象或其完整对象或其子对象的泛左值,
[...]
所以这也是合法的:
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 浮点引用无关紧要,因为允许 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)
(4.1)const_cast (expr.const.cast),
(4.2) static_cast (expr.static.cast),
(4.3)static_cast 后跟 const_cast,
(4.4)reinterpret_cast (expr.reinterpret.cast),或
(4.5)reinterpret_cast 后跟 const_cast,
可以使用显式类型转换的强制转换表示法来执行。应用相同的语义限制和行为,[...]。
所以const_cast<int&>(static_cast<int const&>(f)) 绝对应该是一个有效的转换序列。
这不起作用的原因实际上是一个非常非常古老的编译器错误。
根据 7.6.3 [expr.cast] 第 4 段,对旧式强制转换的一种可能解释是 static_cast 后跟 const_cast。因此,人们会期望以下示例中标记为 #1 和 #2 的表达式具有相同的有效性和含义:
struct S {
operator const int* ();
};
void f(S& s) {
const_cast<int*>(static_cast<const int*>(s)); // #1
(int*) s; // #2
}
但是,许多实现在 #2 上发出错误。
(T*)x 是否应该被解释为 const_cast<T*>(static_cast<const volatile T*>(x)) 之类的东西
结果是:
基本原理(2009 年 7 月):
根据措辞的直接解释,该示例应该有效。这似乎只是一个编译器错误。
所以标准同意你的结论,只是没有编译器真正实现了这种解释。
2.2 编译器错误票
gcc 和 clang 已经有关于这个问题的开放错误:
2.3 为什么这么多年还没有修复?
我不知道,但考虑到他们现在大约每 3 年就必须实施一项新标准,每次都会对语言进行大量更改,因此忽略大多数程序员可能永远不会遇到的问题似乎是合理的。
请注意,这只是原始类型的问题。我的猜测是该错误的原因是由于左值到右值的转换规则,对于那些 cv 限定符可以被 static_cast / reinterpret_cast 删除。
如果T是非类类型,则prvalue的类型是T的cv-unqualified version。否则,prvalue的类型是T。
请注意,此错误仅影响非类类型,对于类类型它会完美运行:
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 开始做这件事(由于严格的别名规则,这将导致未定义的行为)