为什么在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::variant
和boost::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);
}
解决方案
从根本上讲,optional
和variant
不允许引用类型的原因是在赋值(在较小程度上,比较)应该为此类情况做什么方面存在分歧。optional
比variant
更容易在示例中显示,因此我将坚持这一点:
int i = 4, j = 5;
std::optional<int&> o = i;
o = j; // (*)
标记行可以解释为:
- 重新绑定
o
,使得&*o == &j
。作为此行的结果,i
和j
本身的值保持不变。 - 通过
o
赋值,这样的&*o == &i
仍然成立,但现在是i == 5
。 - 完全禁止分配。
直通赋值是只将=
推送到T
的=
所获得的行为,重新绑定是一种更完善的实现,也是您真正想要的(另请参阅this question,以及关于Reference Types的Matt Calabrese演讲)。
解释(1)和(2)之间差异的另一种方式是我们可以如何在外部实现这两者:
// rebind
o.emplace(j);
// assign through
if (o) {
*o = j;
} else {
o.emplace(j);
}
Boost.Optional文档提供了以下基本原理:
为初始化的可选引用的赋值选择了重新绑定语义,以提供初始化状态之间的一致性,即使与裸C++引用的语义不一致也是如此。的确,无论何时初始化,
optional<U>
都会尽量表现得像U一样;但是在U
为T&
的情况下,这样做会导致行为w.r.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&
那样进行赋值,则如果不显式处理以前的初始化状态,您将永远无法使用Optional的赋值,除非您的代码能够正常运行,无论在赋值之后,a
是否将同一对象作为b
的别名。也就是说,您必须进行区分才能保持一致。
如果在您的代码中没有重新绑定到另一个对象的选项,那么很可能第一次绑定也不是。在这种情况下,禁止赋值给未初始化的
optional<T&>
。很可能在这种情况下,必须已经初始化左值是一个前提条件。如果不是,那么第一次绑定是可以的,而重新绑定是不可以的,这在我看来是非常不可能的。在这种情况下,您可以直接赋值,如下所示:assert(!!opt); *opt=value;
对该行应该做什么缺乏一致意见意味着完全禁止引用会更容易,这样和的大多数值至少可以在C++17中使用,并开始发挥作用。在C++17中,
optional
和variant
的大部分值至少可以用于C++17并开始有用。引用总是可以在以后添加-或者说争论是这样进行的。
相关文章