为什么在std :: variant中禁止引用?

olq_plo:

我经常使用boost::variant并且非常熟悉它。boost::variant不会以任何方式限制有界类型,尤其是它们可以是引用:

#include <boost/variant.hpp>
#include <cassert>
int main() {
  int x = 3;
  boost::variant<int&, char&> v(x); // v can hold references
  boost::get<int>(v) = 4; // manipulate x through v
  assert(x == 4);
}

我有一个真实的用例,可以将引用的变体用作其他数据的视图。

然后,我惊讶地发现,它std::variant不允许将引用作为有界类型,std::variant<int&, char&>也无法编译,并且在这里明确指出:

不允许变体保存引用,数组或类型为void。

我想知道为什么不允许这样做,我看不到技术原因。我知道的实现std::variantboost::variant不同,所以也许它做呢?还是作者认为这是不安全的?

PS:我真的不能处理限制的std::variant使用std::reference_wrapper,因为包装不参考不允许从基本类型分配。

#include <variant>
#include <cassert>
#include <functional>

int main() {
  using int_ref = std::reference_wrapper<int>;
  int x = 3;
  std::variant<int_ref> v(std::ref(x)); // v can hold references
  static_cast<int&>(std::get<int_ref>(v)) = 4; // manipulate x through v, extra cast needed
  assert(x == 4);
}
巴里:

从根本上说optionalvariant不允许引用类型的原因是,在这种情况下应该分配什么(在较小程度上是比较)上存在分歧。optionalvariant在示例中展示更容易,所以我坚持:

int i = 4, j = 5;
std::optional<int&> o = i;
o = j; // (*)

标记的行可以解释为:

  1. 重新绑定o,这样&*o == &j作为此行的结果,值ij本身仍然改变。
  2. 通过分配o&*o == &i现在仍然如此i == 5
  3. 完全禁止分配。

分配通是您只需按下拿到的行为=,通过对T=,重新绑定是一种更完善的实现,是你真正想要的(也看到了这个问题,以及对马特·卡拉布雷斯谈话引用类型)。

解释(1)和(2)之间差异的另一种方式是我们如何在外部实现两者:

// rebind
o.emplace(j);

// assign through
if (o) {
    *o = j;
} else {
    o.emplace(j);
}

Boost.Optional文档提供了这样的理由:

选择重绑定语义来分配初始化的可选引用,即使在与裸C ++引用的语义缺乏一致性的情况下,也可以提供初始化状态之间的一致性。的确,optional<U>无论何时初始化,都力求尽可能地表现与U一样。但在Uis 的情况下T&,这样做会导致左值初始化状态的行为不一致。

想象一下optional<T&>将分配转发给引用对象(从而更改引用对象的值但不重新绑定),并考虑以下代码:

optional<int&> a = get();
int x = 1 ;
int& rx = x ;
optional<int&> b(rx);
a = b ;

作业是做什么的?

如果a未初始化,答案是明确的:它结合x(我们现在有另一个参考x)。但是如果a已经初始化怎么办?它将改变被引用对象的值(无论是什么);这与其他可能的情况不一致。

如果optional<T&>可以像分配一样T&进行分配,则除非显式地处理代码是否能够在赋值之后a使用相同的对象作为别名,否则您将无法在不显式处理先前的初始化状态的情况下使用Optional的赋值b

也就是说,您必须进行区分才能保持一致。

如果在您的代码中重新绑定到另一个对象不是一种选择,那么很可能第一次也不绑定。在这种情况下,应禁止分配给未初始化的对象 optional<T&>在这种情况下,很有可能必须先初始化左值。如果不是,则第一次绑定就可以,而重新绑定则不能,这在IMO中是不太可能的。在这种情况下,您可以直接分配值本身,如下所示:

assert(!!opt);
*opt=value;

对该行应该做什么没有达成共识,这意味着更容易完全拒绝引用,因此optionaland的大部分价值variant至少可以使它对C ++ 17有用,并开始变得有用。引用总是可以在以后添加-否则争论就过去了。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么C ++ 20中引入了std :: ssize()?

为什么C ++ 17中没有std :: construct_at?

为什么在C ++ 20中std :: move没有[[nodiscard]]?

为什么在C ++ 17中添加了std :: reduce?

为什么std :: sort不按引用接受比较器?

为什么C ++ 11中的std :: initializer_list重载了std :: begin()和std :: end()?

为什么std :: declval添加引用?

为什么std :: ios_base :: Init中的“ Init”是大写的?

为什么std :: move具有向前引用?

为什么std :: tuple分解为右值引用

为什么在C ++ 17中没有std :: future :: then?

为什么在std :: vector擦除中需要begin()?

为什么使用{}来访问std :: hash中的operator()?

为什么在GCC中对std :: set的推论失败?

为什么std :: shuffle需要右值引用?

为什么在void函数中接受return throw std :: exception()?

在Rust中,为什么std :: iter :: Iterator的min函数返回引用?

为什么我通过std :: variant获得std :: bad_variant_access?

C ++中的std :: vector与[]比较慢-为什么?

为什么在`std :: add_pointer_t`中需要SFINAE?

为什么在以下示例中std :: map :: insert失败?

为什么std :: optional <boost :: variant <A,bool >>不能从`std :: optional <A>`成功构造?

为什么在std中未定义rand_r?

为什么std :: string的const访问器返回引用?

为什么我的对象没有插入std :: set中?

为什么允许 std::variant 多次持有相同的类型?

为什么 std::unique_ptr 没有优化而 std::variant 可以?

为什么我不能将 std::map 的条目解压到引用中?

为什么在移动赋值中需要 std::variant 成为 valueless_by_exception?