簡體   English   中英

為什么這種 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 ,這應該被認為是,按順序:

  • 一個const_cast
  • 一個static_cast
  • 一個static_cast后跟一個const_cast
  • 一個reinterpret_cast ,或
  • 一個reinterpret_cast后跟一個const_cast

顯然,一個static_cast<int const&>后跟一個const_cast<int&>可行的,並且會產生一個值為0int 但是所有編譯器都將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 代替?

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 鑄件(重點是我的)

    (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)如果存在從ET的隱式轉換序列 ( over.best.ics ),則表達式E可以顯式轉換為類型T ,[...]。
    如果T是引用類型,則效果與執行聲明和初始化相同
    T t(E);
    對於一些發明的臨時變量t ([dcl.init]),然后使用臨時變量作為轉換的結果。

    • 在我們的例子中,聲明看起來像這樣:
      const int& t(f);
    • 為了簡短起見,我不會在這里詳細說明整個轉換過程,您可以閱讀12.2.4.2 隱式轉換序列中的確切細節
    • 在我們的例子中,轉換序列將包括 2 個步驟:
      • 將 glvalue 浮點數轉換為純右值(這也允許我們擺脫const
        7.3.2 左值到右值的轉換(強調我的)

        (1)非函數、非數組類型T的泛左值可以轉換為純右值。 如果T是不完整類型,則需要這種轉換的程序是非良構的。 如果T是非類類型,則純右值的類型是T的 cv 非限定版本 否則,純右值的類型為T

        鑒於float是非類類型,這允許我們將ffloat const&轉換為float&&

      • 從浮點數轉換為整數
        7.3.11 浮點-積分轉換

        (1)浮點類型的純右值可以轉換為 integer 類型的純右值。 轉換截斷; 也就是說,小數部分被丟棄。 如果截斷的值不能在目標類型中表示,則行為未定義。

        所以我們最終得到了一個從f很好地轉換的int值。

  • 所以static_cast<>部分的最終結果是一個左值int const&

1.2 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 類型T1T2 ,如果指向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。

1.3 結論

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
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 顯式類型轉換(強制轉換符號)

(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))絕對應該是一個有效的轉換序列。

這不起作用的原因實際上是一個非常非常古老的編譯器錯誤。

2.1 它甚至是一個open-std.org 問題 (#909)

根據 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 是非類類型,則純右值的類型是 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)。

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開始做這件事(由於嚴格的別名規則,這將導致未定義的行為)

這是未定義的行為。 但要回答這個問題,據我所知:

您將const丟棄,然后將其reinterpret_castint&
它不是static_cast ,因為它已經是對不能與int&指針互轉換的左值的引用。

reinterpret_cast的結果是未定義的行為。 它違反了嚴格的別名規則

您可以在嘗試使用std::is_pointer_interconvertible_base_of_v<>之前進行檢查。 請參閱: cppreference.com

如果我們忽略const它仍然沒有意義。 您可以參考純右值來初始化變量,它屬於臨時 object 異常的生命周期,這很有趣。 但就是這樣。
請參閱: 參考初始化,然后在您的生活中永遠不要這樣做。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM