Why are references forbidden in std::variant?

  • A+
Category:Languages

I use boost::variant a lot and am quite familiar with it. boost::variant does not restrict the bounded types in any way, in particular, they may be references:

#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); } 

I have a real use-case for using a variant of references as a view of some other data.

I was then surprised to find, that std::variant does not allow references as bounded types, std::variant<int&, char&> does not compile and it says here explicitly:

A variant is not permitted to hold references, arrays, or the type void.

I wonder why this is not allowed, I don't see a technical reason. I know that the implementations of std::variant and boost::variant are different, so maybe it has to do with that? Or did the authors think it is unsafe?

PS: I cannot really work around the limitation of std::variant using std::reference_wrapper, because the reference wrapper does not allow assignment from the base type.

#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); } 

 


Fundamentally, the reason that optional and variant don't allow reference types is that there's disagreement on what assignment (and, to a lesser extent, comparison) should do for such cases. optional is easier than variant to show in examples, so I'll stick with that:

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

The marked line can be interpreted to either:

  1. Rebind o, such that &*o == &j. As a result of this line, the values of i and j themselves remain changed.
  2. Assign through o, such &*o == &i is still true but now i == 5.
  3. Disallow assignment entirely.

Assign-through is the behavior you get by just pushing = through to T's =, rebind is a more sound implementation and is what you really want (see also this question, as well as a Matt Calabrese talk on Reference Types).

A different way of explaining the difference between (1) and (2) is how we might implement both externally:

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

Lack of agreement on what that line should do meant it was easier to just disallow references entirely, so that most of the value of optional and variant can at least make it for C++17 and start being useful. References could always be added later - or so the argument went.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: