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

时间:2018-03-22 11:53:17

标签: 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

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

5 个答案:

答案 0 :(得分:44)

std::any_casttypeid指定。在此引用cppreference

  

如果所请求的std::bad_any_cast的{​​{1}}有效,则会typeid   与操作数的内容不匹配。

由于typeid不允许实现“弄清楚”隐式转换是可能的,因此(我知道)ValueType无法知道它是否可能。

除此之外,any_cast提供的类型擦除依赖于仅在运行时可用的信息。而且这些信息并不像编译器用于计算转换的信息那么丰富。这就是C ++ 17中类型擦除的代价。

答案 1 :(得分:19)

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

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

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

答案 2 :(得分:3)

std::any 已使用type-erasure实现。这是因为它可以存储任何类型,并且不能成为模板。目前,C ++中没有其他功能可以实现这一目标。

这意味着std::any将存储一个类型擦除的指针,void*std::any_cast会将该指针转换为指定的类型。它只是使用typeid进行完整性检查,以检查您将其投射到的类型是否存储在任何类型中。

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

std::any_cast<long>(a);

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

std::any必须存储存储在其中的对象的实际类型,这是不可能的。您现在无法在C ++中存储类型。它可以维护一个类型列表及其各自的typeid并切换它们以获取当前类型并执行隐式转换。但是对于你要使用的每个单一类型,没有办法做到这一点。用户定义的类型无论如何都不会起作用,你必须依赖诸如宏之类的东西来注册&#34;您的类型并为其生成适当的开关案例 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 ++的口头禅是“你不会为你不能使用的东西买单”#34;所以不,目前没有办法实现这一目标。这种方法也是&#34; hacky&#34;并且非常脆弱(如果你忘记注册一个类型会发生什么)。简而言之,做这样的事情可能带来的好处一开始并不值得。

  

是否允许隐式转换(如果std :: any保存的确切类型未知)?

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

1 :实际上并不知道这是否可能,我在宏(ab)使用方面不是那么好。您也可以为自定义实现硬编码类型,或者使用单独的工具。

答案 3 :(得分:1)

如果请求类型的类型ID与存储类型的类型ID不同,则可以通过尝试偶然隐式转换来实现。但这会涉及成本,因此违反了"not pay for what you don't use"原则。例如,另一个any缺点是无法存储数组。

std::any("blabla");

会起作用,但会存储char const*,而不是数组。您可以在自己的自定义any中添加此类功能,但是您需要通过执行以下操作来存储指向字符串文字的指针:

any(&*"blabla");

这有点奇怪。标准委员会的决定是妥协,永远不会让每个人满意,但幸运的是你可以选择实施自己的any

例如,您也可以将any扩展为存储,然后调用类型删除的functors,但标准也不支持此功能。

答案 4 :(得分:0)

这个问题提出得不好;原则上 可以隐式转换为正确的类型,但禁用。 可能存在此限制,以保持一定的安全性或模仿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" );
 }