I try to use a std::variant with an enum as part of the possible types. I have a compile error and i don't find the reason. If I use any other type instead of the enum, the code works. Here a part of my code:
#include <variant>
#include <iostream>
enum myEnum
{
INT8,
INT32
};
using value_t = std::variant<unsigned char , int, myEnum>;
template<class T, typename U = void>
struct visitHelper;
template<class T>
struct visitHelper <T>
{
T &v;
visitHelper(T &v): v(v){}
void operator()(const T v){ this->v = v; }
};
template <typename T> visitHelper(T &v) -> visitHelper<T>;
template<class T>
void updateValue(T &v, value_t value)
{
std::visit(visitHelper(v), value);
}
int main()
{
/* uncomment this block will cause an compiler error
myEnum e;
updateValue(e, INT32);
std::cout << e << std::endl;
*/
int i;
updateValue(i, 17);
std::cout << i << std::endl;
}
Why this code doesn't compile if I uncomment the block?
* First edit *
I modified the code to look like this and now it's working.
#include <variant>
#include <iostream>
enum myEnum
{
INT8,
INT32
};
using value_t = std::variant<unsigned char , int, myEnum>;
template<class T, typename U = void>
struct visitHelper;
template<class T>
struct visitHelper <T, std::enable_if_t< std::is_arithmetic_v< T > > >
{
T &v;
visitHelper(T &v): v(v){}
void operator()(const T v){ this->v = v; }
};
template<class T>
struct visitHelper <T, std::enable_if_t< std::is_enum_v< T > > >
{
T &v;
visitHelper(T &v): v(v){}
void operator()(const T v){ this->v = v; }
void operator()(const int v){ this->v = static_cast<T>(v); }
void operator()(...){ }
};
template <typename T> visitHelper(T &v) -> visitHelper<T>;
template<class T>
void updateValue(T &v, value_t value)
{
std::visit(visitHelper(v), value);
}
int main()
{
myEnum e;
updateValue(e, INT32);
std::cout << e << std::endl;
int i;
updateValue(i, 18);
std::cout << i << std::endl;
}
Let's start with a much simpler example:
enum myEnum
{
INT8,
INT32
};
int foo1(myEnum bar1)
{
return bar1;
}
myEnum foo2(int bar2)
{
return bar2;
}
If you try to compile this, your compiler will report a compilation error with foo2()
, but not foo1()
. This tells you that an enumeration can be implicitly converted to an integral value, but an integral value cannot be implicitly converted to an enumeration value. gcc 8.2, in -std=c++17
mode, issues a pretty clear error message:
invalid conversion from ‘int’ to ‘myEnum’ [-fpermissive]
Let's stop here, and don't proceed any further until you understand this. Integral values cannot be implicitly converted to enumerations, but enumerations can be converted to integral values.
Now, let's work out what's happening here:
myEnum e;
updateValue(e, INT32);
Your deduction guide will instantiate visitHelper
using myEnum
. You are, in essence, creating the following template instance:
struct visitHelper<myEnum>
{
myEnum &v;
visitHelper(myEnum &v): v(v){}
void operator()(const myEnum v){ this->v = v; }
};
You're passing an instance of this object to std::visit
, and what's being visited is an instance of:
std::variant<unsigned char , int, myEnum>;
The requirement for a visitor (somewhat generalized) is that it must provide an ()
overload for every type stored in the variant being visited.
That is, the ()
operator must accept unsigned char
, int
, and myEnum
values. But the only ()
operator in your visitor takes a myEnum
parameter, and therefore the attempt to pass an int
(or an unsigned char
) fails, because this implicit conversion is not allowed.
In your working case, your template class gets instantiated with T=int
, and the ()
overload takes an int
parameter, and since an instance of every type in the variant can be implicitly converted to an int
, that works.