为什么std :: get for variant抛出失败而不是未定义的行为?

Den*_*kiy 19 c++ variant c++17

根据投掷的cppreference std::get,如果包含的类型不是预期的类型.这意味着标准库必须检查每次访问(libc ++).variantstd::bad_variant_accessvariant

这个决定的理由是什么?为什么它不是未定义的行为,就像C++中的其他地方一样?我可以解决吗?

Jus*_*tin 11

当前的API std::variant没有未经检查的版本std::get.我不知道为什么这样做是标准化的; 我说的任何东西都只是在猜测.

但是,您可以通过编写接近所需的行为*std::get_if<T>(&variant).如果在那时variant没有保留T,则std::get_if<T>返回nullptr,因此解除引用它是未定义的行为.因此编译器可以假设变体成立T.


实际上,这不是编译器最容易的优化.与简单标记的联合相比,它发出的代码可能不太好.以下代码:

int const& get_int(std::variant<int, std::string> const& variant)
{
    return *std::get_if<int>(&variant);
}
Run Code Online (Sandbox Code Playgroud)

用clang 5.0.0发出这个:

get_int(std::variant<int, std::string> const&):
  xor eax, eax
  cmp dword ptr [rdi + 24], 0
  cmove rax, rdi
  ret
Run Code Online (Sandbox Code Playgroud)

它正在比较变量的索引并在索引正确时有条件地移动返回值.尽管指数的UB不正确,但clang目前无法优化比较.

有趣的是,返回an int而不是引用可以优化检查:

int get_int(std::variant<int, std::string> const& variant)
{
    return *std::get_if<int>(&variant);
}
Run Code Online (Sandbox Code Playgroud)

发出:

get_int(std::variant<int, std::string> const&):
  mov eax, dword ptr [rdi]
  ret
Run Code Online (Sandbox Code Playgroud)

您可以使用__builtin_unreachable()或帮助编译器__assume,但gcc是目前唯一能够在您执行此操作删除检查的编译器.


Dan*_*iel 9

为什么它不是未定义的行为,就像c ++中的其他地方一样?我可以解决吗?

是的,有一个直接的解决方法.如果您不想要类型安全,请使用plain union而不是a std::variant.正如你在引用中所说:

类模板std :: variant表示类型安全联合.

目的union是让一个对象可以从多种不同类型中的一种中获取值.union在任何给定时间,只有一种类型的"有效",具体取决于已分配的成员变量:

union example {
   int i;
   float f;
};

// code block later...
example e;
e.i = 10;
std::cout << e.f << std::endl; // will compile but the output is undefined!
Run Code Online (Sandbox Code Playgroud)

std::variant概括了一段union时间添加类型安全性以帮助确保您只访问正确的数据类型.如果您不想要这种安全性,可以随时使用union.

这个决定的理性是什么?

我个人不知道这个决定的理由是什么,但你总是可以看一下C++标准化委员会的论文,以便对这个过程有所了解.

  • 我认为很清楚 - 创建一个类型安全的`union`版本.在C中编写`union`s时,我记得总是将`union`对象与`int`或`enum`配对,以便我可以存储关于union设置中哪种类型的信息.否则,如果我从错误的数据成员读取,我将冒险UB.这里,`std :: variant`为union-type对象提供错误处理和类型调用,因此您不必自己实现(除了检查类型是否正确). (5认同)
  • @kkm如果你关心的只是存储`int`s,`float`s等等,那么这是一个很容易替代的东西.但是,如果其中一个替代品是`std :: string`或任何其他非平凡类型呢?你最终会得到类似[this](/sf/ask/2134504921/)的内容.你也失去了其他很好的功能,如探视.是的,你可以自己编写所有这些内容,但那可以解决任何要求采取替代方法的问题. (4认同)
  • 你是认真的吗?如果用`union`替换`variant`那么简单,为什么前者存在呢? (2认同)
  • 我不理解downvotes.`variant`是一个`union`加上类型安全; 当然,`变体'减去类型安全是一个联盟.我必须赞成这个答案,但@DanielDay请稍微修改一下这个语言.你的意思是说"如果你不想要类型安全,请使用普通联合而不是变体",但实际上你在第一句中反过来说. (2认同)
  • @kkm:不是黑白的;通过切换到`union`,除了那种类型检查之外,你还放弃了一个_lot_。但是,我同意 Daniel 的观点,如果您真的非常渴望它,这至少是一种有效的解决方法。 (2认同)

Den*_*kiy 1

我想我找到了!

似乎可以在提案中的“与修订版 5 的差异”下找到原因:

Kona 妥协:f !v.valid(),使 get<...>(v) 和 Visit(v) 抛出。

含义 - 该变体必须抛出“values_by_exception”状态。使用相同的if我们总是可以抛出。

即使知道这个道理,我个人也想避免这种检查。贾斯汀的回答*get_if似乎对我来说足够好(至少对于库代码)。