![](/img/trans.png)
[英]Does the C++ specification say how types are chosen in the static_cast/const_cast chain to be used in a C-style 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 ,這應該被認為是,按順序:
- 一個
const_cast
,- 一個
static_cast
,- 一個
static_cast
后跟一個const_cast
,- 一個
reinterpret_cast
,或- 一個
reinterpret_cast
后跟一個const_cast
,
顯然,一個static_cast<int const&>
后跟一個const_cast<int&>
是可行的,並且會產生一個值為0的int
。 但是所有編譯器都將i
初始化為42 ,這表明他們采用了reinterpret_cast<int const&>
的最后一個選項,然后是const_cast<int&>
。 為什么?
相關: 在 C++ 中,C 風格的轉換能否調用轉換 function 然后拋棄 constness? , 為什么 (int&)0 格式不正確? , C++ 規范是否說明了如何在 static_cast/const_cast 鏈中選擇類型以用於 C 樣式轉換? , 類型雙關語與 (float&)int 作品, (float const&)int 轉換像 (float)int 代替?
const_cast<int&>(static_cast<int const&>(f))
有效 c++(int&)f
應該有相同的結果const_cast<int&>(static_cast<int const&>(f))
有效static_cast
讓我們從static_cast<int const&>(f)
開始:
讓我們檢查一下該演員表的結果是什么:
7.6.1.9 Static 鑄件(重點是我的)
(1)表達式
static_cast<T>(v)
的結果是將表達式v
轉換為類型T
的結果。 如果T
是左值引用類型或對 function 類型的右值引用,則結果為左值; 如果T
是對 object 類型的右值引用,則結果是一個 xvalue; 否則,結果為純右值。 static_cast 運算符不應拋棄常量( expr.const.cast )。
int const&
是左值引用類型,因此static_cast<>()
的結果必須是某種左值。
然后讓我們找出實際發生的轉換:
7.6.1.9 Static 鑄件
(4)如果存在從
E
到T
的隱式轉換序列 ( over.best.ics ),則表達式E
可以顯式轉換為類型T
,[...]。
如果T
是引用類型,則效果與執行聲明和初始化相同T t(E);
對於一些發明的臨時變量t
([dcl.init]),然后使用臨時變量作為轉換的結果。
const int& t(f);
將 glvalue 浮點數轉換為純右值(這也允許我們擺脫const
)
7.3.2 左值到右值的轉換(強調我的)
(1)非函數、非數組類型
T
的泛左值可以轉換為純右值。 如果T
是不完整類型,則需要這種轉換的程序是非良構的。 如果T
是非類類型,則純右值的類型是T
的 cv 非限定版本。 否則,純右值的類型為T
。
鑒於float
是非類類型,這允許我們將f
從float const&
轉換為float&&
。
從浮點數轉換為整數
7.3.11 浮點-積分轉換
(1)浮點類型的純右值可以轉換為 integer 類型的純右值。 轉換截斷; 也就是說,小數部分被丟棄。 如果截斷的值不能在目標類型中表示,則行為未定義。
所以我們最終得到了一個從f
很好地轉換的int
值。
所以static_cast<>
部分的最終結果是一個左值int const&
。
const_cast
現在我們知道static_cast<>
部分返回什么,我們可以關注const_cast<int&>()
:
結果類型需要是:
7.6.1.11 常量轉換(強調我的)
(1)表達式
const_cast<T>(v)
的結果是T
類型。 如果T
是對 object 類型的左值引用,則結果是左值; 如果T
是對 object 類型的右值引用,則結果是一個 xvalue; 否則,結果為純右值,並且對表達式v
執行左值到右值、數組到指針和函數到指針的標准轉換。 下面列出了可以使用 const_cast 顯式執行的轉換。 不應使用 const_cast 顯式執行其他轉換。
static_cast<>
產生一個左值,因此const_cast<>
的結果也必須是一個左值。
const_cast<>
做了什么轉換? 7.6.1.11 常量轉換(強調我的)
(4)對於兩個 object 類型
T1
和T2
,如果指向T1
的指針可以使用 const_cast 顯式轉換為類型“指向T2
的指針”,則還可以進行以下轉換:
(4.1)T1
類型的左值可以使用const_cast<T2&>
顯式轉換為 T2 類型的左值T2
(4.2)T1
類型的泛左值可以使用強制轉換const_cast<T2&&>
顯式轉換為T2
類型的 xvalue; 和
(4.3)如果T1
是 class 類型,則可以使用強制轉換const_cast<T2&&>
將T1
類型的純右值顯式轉換為T2
類型的 xvalue。
如果操作數是泛左值,則引用 const_cast 的結果引用原始 object,否則引用應用臨時實現轉換的結果。
因此const_cast<>
會將左值const int&
轉換為int&
左值,這將引用相同的 object。
const_cast<int&>(static_cast<int const&>(f))
格式正確,將產生一個左值 int 引用。
您甚至可以根據6.7.7 臨時對象延長引用的生命周期
(6)引用綁定的臨時 object 或臨時 object 是子對象的完整 object,如果 glvalue 是通過一個引用獲得的引用綁定的生命周期,則該子對象的完整 object 將持續存在以下的:
[...]
- (6.6)一個
- (6.6.1)const_cast
( expr.const.cast ),
[...]
在沒有用戶定義的轉換的情況下,將作為這些表達式之一的泛左值操作數轉換為引用由操作數指定的 object 或其完整的 object 或其子對象的泛左值,
[...]
所以這也是合法的:
float const& f = 1.2f;
int& i = const_cast<int&>(static_cast<int const&>(f));
i++; // legal
return i; // legal, result: 2
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);
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
不起作用您在技術上是正確的,因為它應該可以工作,因為允許 c 樣式轉換執行此轉換序列:
(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 月):根據對措辭的直接解釋,該示例應該有效。 這似乎只是一個編譯器錯誤。
所以標准同意你的結論,只是沒有編譯器真正實現了這種解釋。
gcc 和 clang 已經存在關於此問題的開放錯誤:
我不知道,但是考慮到他們現在大約每 3 年就必須實施一個新標准,每次對語言進行大量更改時,忽略大多數程序員可能永遠不會遇到的問題似乎是合理的。
請注意,這只是原始類型的問題。 我的猜測是該錯誤的原因是由於左值到右值的轉換規則,對於那些 cv 限定符可以被static_cast
/ reinterpret_cast
刪除。
如果 T 是非類類型,則純右值的類型是 T 的cv 非限定版本。否則,純右值的類型是 T。
請注意,此錯誤僅影響非類類型,對於類類型,它將完美地工作:
struct B { int i; };
struct D : B {};
D d;
d.i = 12;
B const& ref = d;
// works
D& k = (D&)ref;
總會有一些邊緣情況在每個和每個編譯器中都沒有正確實現,如果它困擾你,你可以提供一個修復,也許他們會將它與下一個版本合並(至少對於 clang 和 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
開始做這件事(由於嚴格的別名規則,這將導致未定義的行為)
這是未定義的行為。 但要回答這個問題,據我所知:
您將const
丟棄,然后將其reinterpret_cast
為int&
。
它不是static_cast
,因為它已經是對不能與int&
指針互轉換的左值的引用。
reinterpret_cast
的結果是未定義的行為。 它違反了嚴格的別名規則。
您可以在嘗試使用std::is_pointer_interconvertible_base_of_v<>
之前進行檢查。 請參閱: cppreference.com
如果我們忽略const
它仍然沒有意義。 您可以參考純右值來初始化變量,它屬於臨時 object 異常的生命周期,這很有趣。 但就是這樣。
請參閱: 參考初始化,然后在您的生活中永遠不要這樣做。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.