1

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;
}
Jonathan
  • 69
  • 1
  • 6
  • I seems strange to go to all that effort, instead of `e = INT32; i = 18;`. What should happen if the value in the variant is of the wrong type? – Caleth Jan 10 '19 at 14:51
  • @Caleth This is a minimal sample of my big code. – Jonathan Jan 10 '19 at 20:05

1 Answers1

4

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.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Can you take a look to my first edit ? Let me know if this can be a good solution. – Jonathan Jan 10 '19 at 13:22
  • 1
    It's only possible to determine if something is "a good solution" if it is specified what exactly is the problem that the proposed solution is for. If the problem is only the compilation error, then if the replacement code compiles then it's obvious that it's a "good solution" for the compilation error. But just because something compiles doesn't mean that it does what the intent of the code is. Remember the golden rule of computer programming: a computer always does what you tell it to do, not what you want it to do. If this code does what you want it to do, then, yes, it's a good solution. – Sam Varshavchik Jan 10 '19 at 13:27