In modern gcc, std::variant doesn't look like a competent replacement for old-fashioned tagged unions.
On x86_64 Linux, it looks like a function with signature `void f(std::variant<int, char>)` expects its (8-byte) argument to be passed by reference, whereas `void f2(tagged_union_of_int_and_char)` passes its argument in rdi.
gcc (-O3) also generates miserable code for calling f(42):
4004f0: 48 83 ec 18 sub $0x18,%rsp
4004f4: 48 89 e7 mov %rsp,%rdi
4004f7: c7 04 24 2a 00 00 00 movl $0x2a,(%rsp)
4004fe: c6 44 24 04 00 movb $0x0,0x4(%rsp)
400503: e8 c8 ff ff ff callq 4004d0 <f>
400508: 48 83 c4 18 add $0x18,%rsp
40050c: c3 retq
as compared with calling f2(42):
400510: 48 bf 00 00 00 00 2a movabs $0x2a00000000,%rdi
400517: 00 00 00
40051a: eb c4 jmp 4004e0 <f2>
std::optional seems to have the same disease; the ABI is different from a plain struct containing a bool and the value and you get worse initialisation code. There's also a build time penalty to using std::optional; on my machine, including the code that uses optionals makes my trivial test program take 320ms to compile and link rather than 50ms.
These look like yet more new C++ features that are worse than what they're trying to replace.