I feel like I'm missing something obvious about int type promotion when assigning to a variant.
On gcc version 9.3.0 (Ubuntu 9.3.0-11ubuntu0~18.04.1), compiling with -std=c++17, the following code fails to compile:
#include <variant>
#include <iostream>
int main()
{
std::variant<long int, bool> v; // works fine if "long" is omitted
long int sanity = 1; // verify that we can assign 1 to a long int; works fine
std::cout << sizeof(sanity) << "\n";
v = 1; // compiler error here: why doesn't this assign to the long int variant of v?
return 0;
}
Error message:
error: no match for ‘operator=’ (operand types are ‘std::variant<long int, bool>’ and ‘int’)
Is there any magic to making this work as intended, without requiring explicit casts in the assignment? Thanks!
Assigning to a variant does not simply assign to the currently active type in the variant. Instead, the type of the right hand side is used to determine which of the possible types (the alternatives) is the best match for the right hand side. Then, that type is assigned to.
Thus,
v = 1;
does not automatically assign to thelong int
value that is already insidev
, but rather, a compile-time computation is done in order to determine whetherlong int
orbool
is a better match forint
(the type of the right hand side). The best match is determined using the overload resolution rules. In other words, we must imagine that two functions existand ask ourselves which function would be called by
f(1)
. If thelong int
overload is selected, thenv = 1
assigns to thelong int
object. If thebool
overload is selected, then thelong int
that is currently insidev
is destroyed and a newbool
object is constructed with the value 1.Unfortunately, this overload resolution is ambiguous:
1
requires an "integral conversion" to match eitherlong int
orbool
. Thus, assigning 1 tov
is a compile-time error. If the alternatives had beenint
andbool
, then theint
alternative would yield an exact match and there would be no ambiguity.This particular example is fixed in C++20: the
bool
alternative is removed from consideration since a narrowing conversion would be required to initialize abool
value from anint
value. Thus, in C++20, this code will always assign to thelong int
alternative (if there is abool
object currently inside the variant, it is destroyed).