top | item 41157026

(no title)

fgkhax | 1 year ago

Yes, you read about std::variant on a blog and think that it is a sum type. Then you try it out and realize that it's a thin (type-safe) wrapper over tagged unions that is at least three times slower and has about 5 unreadable alternatives that replace simple switch statements.

Then you find out that members of a "variant" are not really variant members but just the individual types that can be assigned to a union. For example, assigning to a non-const reference does not work (and obviously cannot work once you realize that std::variant is just syntax sugar over a tagged union).

Most of these new additions since C++11 are just leaky abstractions and wrappers.

discuss

order

gpderetta|1 year ago

> and obviously cannot work once you realize that std::variant is just syntax sugar over a tagged union

It would be easy to make it work, there isn't necessarily a strict relation between the template parameter and the actual stored object. Not having reference variant members was a conscious decision, same as optional<T&>. Hopefully this will be fixed in the future.

nly|1 year ago

A few code snippets of what you see as weaknesses of std::variant may be appropriate, as I couldn't figure out your complaint. Assigning to a variant taken by non-const& works fine for me.

I personally would have liked to see recursive variant types and multi-visitation (as supported by boost::variant).

tlhand|1 year ago

std::variant is not a true algebraic data type, since the individual element constructors do not construct the variant type automatically. Compare to OCaml, written in a verbose and unidiomatic way that is similar to C++:

  # type foo = Int of { n : int } | Float of { f : float };;
  type foo = Int of { n : int; } | Float of { f : float; }
  # Int { n = 10 };;
  - : foo = Int {n = 10}
  # let r = ref (Int { n = 10 });;
  val r : foo ref = {contents = Int {n = 10}}
Notice that the constructor Int { n = 10 } automatically produces a foo type and assigning to a mutable ref works.

The same in C++, using assignment to a pointer to avoid the lvalue ref error that is irrelevant to this discussion:

  #include <variant>
  
  struct myint {
    int n;
    myint(int n) : n(n) {}
  };

  struct myfloat {
    float f;
    myfloat(float f) : f(f) {}
  };

  using foo = std::variant<myint, myfloat>;

  int
  main()  
  {
    const foo& x = myint{10}; // works
    foo *z = new myint{10}; // error: cannot convert ‘myint*’ to ‘foo*
  }

As stated above, this obviously cannot work since C++ has no way of specifying a myint constructor that -- like in OCaml -- automatically produces the variant type foo.

C++ would need true algebraic data types with compiler support (that would hopefully be as fast as switch statements). To be useful, they would need a nice syntax and not some hypothetical abomination like:

  using foo = std::variant<myint, myfloat> where
  struct myint of foo { ... };

amluto|1 year ago

I think the comment means:

    std::variant<int&, etc>
does not work well.