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

时间:2019-01-16 13:53:18

标签: c++ boost c++17 variant

我经常使用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&>无法编译,并且显式显示here

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

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

PS:由于引用包装器不允许从基本类型进行赋值,因此我无法真正解决std::variantstd::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);
}

1 个答案:

答案 0 :(得分:10)

从根本上讲,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}}所获得的行为,重新绑定是一种更合理的实现,并且是您真正想要的(请参见{ {3}},以及关于this question的Matt Calabrese演讲)。

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

=

Reference Types文档提供了以下理由:

  选择

用于初始化初始化可选引用的重新绑定语义是为了在初始化状态之间提供一致性,即使是以与裸C ++引用的语义缺乏一致性为代价的。的确,// rebind o.emplace(j); // assign through if (o) { *o = j; } else { o.emplace(j); } 会尽力在初始化时尽其所能。但是在optional<U>U的情况下,这样做会导致左值初始化状态出现不一致的行为。

     

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

optional<T&>
     

作业是做什么的?

     

如果optional<int&> a = get(); int x = 1 ; int& rx = x ; optional<int&> b(rx); a = b ; 是未初始化的 ,答案很明确:它绑定到a(我们现在还有对x的引用)。但是,如果a已经被初始化怎么办?它将改变被引用对象的值(无论是什么);这与其他可能的情况不一致。

     

如果x就像optional<T&>一样进行赋值,那么除非您的代码能够在赋值后{{ 1}}别名是否与T&相同。

     

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

     

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

b

对于该行应该做什么没有达成共识,这意味着更容易完全禁止引用,因此optional<T&>assert(!!opt); *opt=value; 的大多数值至少可以使其适用于C ++ 17并开始变得有用。引用总是可以在以后添加-否则争论就过去了。