簡體   English   中英

為什么std :: any_cast不支持隱式轉換?

[英]why doesn't std::any_cast support implicit conversion?

為什么當從實際存儲類型到請求類型的隱式轉換可能時, std::any_cast拋出std::bad_any_cast異常?

例如:

std::any a = 10;  // holds an int now
auto b = std::any_cast<long>(a);   // throws bad_any_cast exception

為什么這是不允許的,是否有允許隱式轉換的解決方法(如果std::any保存的確切類型未知)?

std::any_cast是根據typeid指定的。 引用cppreference

如果請求的ValueTypetypeid與操作數的內容不匹配,則拋出std::bad_any_cast

由於typeid不允許實現“弄清楚”隱式轉換是可能的,所以沒有辦法(據我所知) any_cast也可以知道它是可能的。

換句話說, std::any提供的類型擦除依賴於僅在運行時可用的信息。 而且這些信息並不像編譯器用於計算轉換的信息那么豐富。 這就是C ++ 17中類型擦除的代價。

要做你想做的事,你需要完整的代碼反思和具體化。 這意味着每種類型的每個細節都必須保存到每個二進制文件(以及每種類型的每個函數的每個簽名!並且每個模板都在任何地方!),當你​​要求從任何類型轉換為類型X時,你將通過有關X的數據進入any,其中包含有關其包含的類型的足夠信息,基本上嘗試將轉換編譯為X並失敗。

有些語言可以做到這一點; 每個二進制文件都帶有IR字節碼(或原始源)和解釋器/編譯器。 在大多數任務中,這些語言往往比C ++慢2倍或更慢,並且具有更大的內存占用量。 有可能沒有這個成本的那些功能,但沒有人擁有我所知道的那種語言。

C ++沒有這種能力。 相反,它在編譯期間忘記了幾乎所有關於類型的事實。 對於任何一個,它會記住一個typeid,它可用於獲得完全匹配,以及如何將其存儲轉換為所述完全匹配。

std::any 必須用type-erasure實現。 那是因為它可以存儲任何類型而不能是模板。 目前C ++中沒有其他功能可以實現這一目標。

這意味着std::any將存儲一個類型擦除的指針, void*std::any_cast會將該指針轉換為指定的類型,就是這樣。 它只是使用typeid進行健全性檢查,以檢查您將其投射到的類型是存儲在any中的類型。

使用當前實現允許隱式轉換是不可能的。 想一想( typeid忽略typeid檢查)。

std::any_cast<long>(a);

a商店的int而不是long std::any應該怎么知道? 它可以將void*轉換為指定的類型,取消引用它並返回它。 將指針從一種類型轉換為另一種類型是嚴格的別名違規並導致UB,所以這是一個壞主意。

std::any必須存儲存儲在其中的對象的實際類型,這是不可能的。 您現在無法在C ++中存儲類型。 它可以維護類型列表及其各自的typeid並切換它們以獲取當前類型並執行隱式轉換。 但是對於您將要使用的每種類型,都無法做到這一點。 用戶定義的類型無論如何都不會起作用,你必須依靠諸如宏之類的東西來“注冊”你的類型並為它生成適當的開關案例1

也許是這樣的:

template<typename T>
T any_cast(const any &Any) {
  const auto Typeid = Any.typeid();
  if (Typeid == typeid(int))
    return *static_cast<int *>(Any.ptr());
  else if (Typeid == typeid(long))
    return *static_cast<long *>(Any.ptr());
  // and so on. Add your macro magic here.

  // What should happen if a type is not registered?
}

這是一個好的解決方案嗎? 不,到目前為止。 這個開關價格昂貴,C ++的口頭禪是“你不為你不使用的東西買單”,所以不,目前無法實現這一目標。 這種方法也是“hacky”而且非常脆弱(如果你忘記注冊一個類型會發生什么)。 簡而言之,做這樣的事情可能帶來的好處首先不值得麻煩。

是否存在允許隱式轉換的解決方法(如果std :: any保存的確切類型未知)?

是的,使用上面提到的宏寄存器方法自己實現std::any (或類似的類型)和std::any_cast 1 我不會推薦它。 如果你不知道並且不知道什么類型的std::any商店並且需要訪問它,你就有可能的設計缺陷。


1 :實際上不知道這是否可能,我在宏(ab)使用方面不是那么好。 您還可以為自定義實現硬編碼類型,或使用單獨的工具。

如果請求類型的類型id與存儲類型的類型id不同,則可以通過嘗試偶然隱式轉換來實現。 但這將涉及成本,因此違反了“不支付你不使用的”原則。 另一any缺點,例如,是不能存儲陣列。

std::any("blabla");

會工作,但它會存儲一個char const* ,而不是一個數組。 你可以在自己的定制添加這樣的功能any ,但隨后你需要存儲指向字符串做文字:

any(&*"blabla");

這有點奇怪。 標准委員會的決定是妥協,從不滿足每個人,但幸運的是你可以選擇實施自己的any

例如,您還可以將any擴展為store,然后調用類型擦除的仿函數 ,但標准也不支持。

這個問題沒有提出來; 隱式轉換為正確的類型原則上是可行的,但是禁用。 這種限制可能存在於維持一定程度的安全性或模仿anyvoid* )的C版本中必要的(通過指針)的顯式強制轉換。 (下面的示例實現表明它是可能的。)

話雖如此,您的目標代碼仍然無效,因為您需要在轉換之前知道確切的類型,但原則上這可以工作:

any a = 10;  // holds an int now
long b = int(a); // possible but today's it should be: long b = any_cast<int>(a);

要表明隱式轉換在技術上是可行的(但可能在運行時失敗):

#include<boost/any.hpp>

struct myany : boost::any{
    using boost::any::any;
    template<class T> operator T() const{return boost::any_cast<T>(*this);}
};

int main(){

    boost::any ba = 10;
//  int bai = ba; // error, no implicit conversion

    myany ma = 10; // literal 10 is an int
    int mai = ma; // implicit conversion is possible, other target types will fail (with an exception)
    assert(mai == 10);

    ma = std::string{"hello"};
    std::string mas = ma;
    assert( mas == "hello" );
 }

暫無
暫無

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

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