3

Consider:

#include <variant>
#include <iostream>

int main()
{
    double foo = 666.666;
    std::variant<double, uint64_t> v = foo;
    std::cout << std::get<uint64_t>(v) << std::endl;
    return 0;
}

This results in:

terminate called after throwing an instance of 'std::bad_variant_access'
  what():  Unexpected index
Aborted (core dumped)

Why?

I have understood that std::variant would be a replacement for union. But if this kind of basic stuff fails what is it good for?

juzzlin
  • 45,029
  • 5
  • 38
  • 50
  • 7
    `union` was never good for type punning. It is always Undefined Behaviour to read different union member than last written one. – Yksisarvinen Apr 15 '20 at 14:14
  • 5
    You don't have a `uint64_t` in the variant. Why do you expect it to give you one? – NathanOliver Apr 15 '20 at 14:15
  • 2
    You assign it a double and read it as an integer. What did you expect? Doing this with a union enters UB land so getting an exception instead is exceptionally better. – Timo Apr 15 '20 at 14:15
  • Also note that type punning though a union in C++ is illegal except if the union has standard layout classes in it that have a shared initial common member sequence and you only access that shared sequence. – NathanOliver Apr 15 '20 at 14:16
  • @NathanOliver doesn't that violate strict aliasing? – Timo Apr 15 '20 at 14:19
  • 1
    `std::variant` is a good replacement for `union` and the fact that is fails here is is an example of why it's a good replacement. You aren't allowed to do that with a `union` but at least with `std::variant` it tells you that you are making a mistake. – François Andrieux Apr 15 '20 at 14:21
  • 1
    @Timo Not for that specific case. See paragraphs 21-25 here: https://timsong-cpp.github.io/cppwp/class#mem-21 – NathanOliver Apr 15 '20 at 14:22
  • It seems that there's no way to simply reinterpret double as uint64_t without compiler warnings about strict aliasing or resulting in UB. Good times. – juzzlin Apr 15 '20 at 14:22
  • 6
    @juzzlin If that is what you want to do, then the C++ way to do that is to `memcpy` the double into a uint64_t. – NathanOliver Apr 15 '20 at 14:24
  • 1
    With C++20 you can use [`std::bit_cast`](https://en.cppreference.com/w/cpp/numeric/bit_cast). – Timo Apr 15 '20 at 14:26
  • @NathanOliver Thanks for the tip :) Didn't think of that route.. – juzzlin Apr 15 '20 at 14:27
  • 3
    "But if this kind of basic stuff fails what is it good for?" I would consider this a strong example of the basic stuff succeeding :-) – Barry Apr 15 '20 at 14:37
  • It also seems that I failed hard reading the documentation.. – juzzlin Apr 15 '20 at 14:57

2 Answers2

7

I have understood that std::variant would be a replacement for union.

Yes. Kind of. But your misunderstanding seems to be with unions in C++. From cppreference:

It's undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.

Note that this is different from C, which is one root cause of a common misunderstanding that accessing an inactive member would also be allowed in C++.

Why?

Because std::variant mimics unions but add some safety and std::get is...

Index-based value accessor: If v.index() == I, returns a reference to the value stored in v. Otherwise, throws std::bad_variant_access. The call is ill-formed if I is not a valid index in the variant.

.

But if this kind of basic stuff fails what is it good for?

Unions in C++ were never meant to be used like that. Unions are rather to save memory when you want to squeeze two or more values into the same memory (but at any time only use one of them). I never met a real use case for them. std::variant however, is a nice addition to C++ types. The bigger picture is that already since long time there were so-called product types, like std::pair<T1,T2>, in the sense that the set of values they can represent is T1 x T2. With std::variant (and std::any) now C++ also has proper (= with a degree of typesafety that you never got from unions) sum types, ie the set of values a std::variant<T1,T2> can represent is T1 + T2 (sorry for using sloppy notation, hope it is clear).

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

std::variant is a “type-safe union”, meaning it’ll check that what you get from it is the type that is last stored in it. In this example you are storing a double but trying to get an uint64_t.

See the docs here: https://en.cppreference.com/w/cpp/utility/variant/get

Type-based value accessor: If v holds the alternative T, returns a reference to the value stored in v. Otherwise, throws std::bad_variant_access. The call is ill-formed if T is not a unique element of Types....

Buddy
  • 10,874
  • 5
  • 41
  • 58