为什么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
保存的确切类型未知)?
答案 0 :(得分:44)
std::any_cast
以typeid
指定。在此引用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
而不是long
。 std::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)
这个问题提出得不好;原则上 可以隐式转换为正确的类型,但禁用。
可能存在此限制,以保持一定的安全性或模仿any
(void*
)的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" );
}