为什么std :: any_cast不支持隐式转换?

Tim*_*imo 33 c++ c++17

当从实际存储类型到请求类型的隐式转换可能时,为什么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
Run Code Online (Sandbox Code Playgroud)

为什么这是不允许的,是否有允许隐式转换的解决方法(如果std::any保存的确切类型未知)?

Sto*_*ica 44

std::any_cast以...表示typeid.引用cppreference:

std::bad_any_cast如果typeid请求ValueType的内容与操作数的内容不匹配则抛出.

既然typeid不允许实现"弄明白"隐式转换是可能的,那么(据我所知)也any_cast无法知道它是否可能.

换句话说,提供的类型擦除std::any依赖于仅在运行时可用的信息.而且这些信息并不像编译器用于计算转换的信息那么丰富.这就是C++ 17中类型擦除的代价.

  • @Timo - 这不可能吗?如果你知道如何,这将是一个重大的突破.我不知道有一种解决方法.要尝试转换,您需要获取已擦除对象的正确类型的引用.但要获得它,您需要事先知道类型(静态).这是鸡和蛋的问题,你想的越多.C++目前不支持任何类型的东西. (4认同)
  • @马蒂厄M。您不应该有两种方法来提取数据,因为 any_cast 旨在模仿 C++-cast。无论如何*你是对的* - 我忽略了深度副本的要求,这使得 std::any 已经需要 vtables,所以是的,添加更广泛的转换支持是可能的(尽管悬空引用的问题会被这个功能放大) (2认同)

Yak*_*ont 19

要做你想做的事,你需要完整的代码反思和具体化.这意味着每种类型的每个细节都必须保存到每个二进制文件(以及每种类型的每个函数的每个签名!并且每个模板都在任何地方!),当你​​要求从任何类型转换为类型X时,你会通过有关X的数据进入any,其中包含有关其包含的类型的足够信息,基本上尝试将转换编译为X并失败.

有些语言可以做到这一点; 每个二进制文件都带有IR字节码(或原始源)和解释器/编译器.在大多数任务中,这些语言往往比C++慢2倍或更慢,并且具有更大的内存占用量.有可能没有这个成本的那些功能,但没有人拥有我所知道的那种语言.

C++没有这种能力.相反,它在编译期间忘记了几乎所有关于类型的事实.对于任何一个,它会记住一个typeid,它可用于获得完全匹配,以及如何将其存储转换为所述完全匹配.


Rak*_*111 5

std::any 必须使用类型擦除来实现。那是因为它可以存储任何类型,不能是模板。目前 C++ 中没有其他功能可以实现这一点。

这也就意味着,std::any将存储类型擦除指针,void*并且std::any_cast将这个指针转换为指定的类型,就是这样。它只是使用typeidbefore 进行完整性检查,以检查您将其转换为的类型是否是存储在 any 中的类型。

使用当前的实现是不可能允许隐式转换的。考虑一下(暂时忽略typeid检查)。

std::any_cast<long>(a);
Run Code Online (Sandbox Code Playgroud)

a存储一个int而不是一个long。应该std::any怎么知道?它可以将其void*强制转换为指定的类型,取消引用并返回它。将指针从一种类型转换为另一种类型是严格的别名违规并导致 UB,所以这是一个坏主意。

std::any将不得不存储存储在其中的对象的实际类型,这是不可能的。你现在不能在 C++ 中存储类型。它可以维护一个类型列表以及它们各自的typeids 并切换它们以获取当前类型并执行隐式转换。但是对于您将要使用的一种类型,都没有办法做到这一点。用户定义的类型无论如何都不起作用,您必须依靠宏等内容来“注册”您的类型并为其生成适当的切换案例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?
}
Run Code Online (Sandbox Code Playgroud)

这是一个很好的解决方案吗?不,到目前为止。转换成本很高,而且 C++ 的口头禅是“不用为不使用的东西付费”,所以不,目前没有办法实现这一点。该方法也是“hacky”且非常脆弱(如果您忘记注册类型会发生什么)。简而言之,做这样的事情可能带来的好处首先不值得麻烦。

是否有允许隐式转换的解决方法(以防 std::any 保存的确切类型未知)?

是的,使用上面提到的宏寄存器方法实现std::any(或类似的类型)和std::any_cast你自己1。不过我不会推荐它。如果您不知道也不能知道std::any存储什么类型并需要访问它,那么您可能存在设计缺陷。


1 : 实际上不知道这是否可能,我不太擅长宏 (ab) 使用。您还可以为您的自定义实现硬编码您的类型或使用单独的工具。

  • 这种通过注册进行的铸造技巧当然是可能的。我已经做到了。但是生成的代码绝对*不是*您希望在标准语言功能中找到的那种代码。它看起来更像是一个特殊的域特定函数(这正是我实现它时的样子) (2认同)