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

Why are references forbidden in std::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::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= 获得的行为,重新绑定是一个更合理的实现,也是您真正想要的(请参阅也 , as well as a Matt Calabrese talk on Reference Types)。

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

// rebind
o.emplace(j);

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

Boost.Optional 文档提供了以下基本原理:

Rebinding semantics for the assignment of initialized optional references has been chosen to provide consistency among initialization states even at the expense of lack of consistency with the semantics of bare C++ references. It is true that optional<U> strives to behave as much as possible as U does whenever it is initialized; but in the case when U is T&, doing so would result in inconsistent behavior w.r.t to the lvalue initialization state.

Imagine optional<T&> forwarding assignment to the referenced object (thus changing the referenced object value but not rebinding), and consider the following code:

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

What does the assignment do?

If a is uninitialized, the answer is clear: it binds to x (we now have another reference to x). But what if a is already initialized? it would change the value of the referenced object (whatever that is); which is inconsistent with the other possible case.

If optional<T&> would assign just like T& does, you would never be able to use Optional's assignment without explicitly handling the previous initialization state unless your code is capable of functioning whether after the assignment, a aliases the same object as b or not.

That is, you would have to discriminate in order to be consistent.

If in your code rebinding to another object is not an option, then it is very likely that binding for the first time isn't either. In such case, assignment to an uninitialized optional<T&> shall be prohibited. It is quite possible that in such a scenario it is a precondition that the lvalue must be already initialized. If it isn't, then binding for the first time is OK while rebinding is not which is IMO very unlikely. In such a scenario, you can assign the value itself directly, as in:

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

对该行应该做什么缺乏一致意见意味着更容易完全禁止引用,因此 optionalvariant 的大部分值至少可以用于 C+ +17 并开始发挥作用。参考总是可以在以后添加——或者争论是这样进行的。