Search code examples
c++templatestype-conversionunionsvariant

How can I use type-safe unions (variants) inside a class with template functions?


I would like to place a std::variant inside a class and return its elements with a template function. Here is an example:

#include <string>
#include <variant>
class Class {
   public:
    std::variant<std::string, double> cont;
    Class() {}
    template <class V> Class(const V v) { cont = v; }
    template <typename V> V fun() {
        if (std::holds_alternative<double>(cont))
            return std::get<double>(cont);
        else if (std::holds_alternative<std::string>(cont))
            return std::get<std::string>(cont);
    }
};

int main() {
    Class c;
    c = 20;
    double d = c.fun<double>();
    return 0;
}

I try to return the elements of the class Class through the template function fun. However, gcc-9.1 refuses to compile the code and tells me

Class.cpp:12:46: error: cannot convert ‘std::__cxx11::basic_string<char>’ to ‘double’ in return
   12 |             return std::get<std::string>(cont);

Why is there any attempt to convert the string (the second return type of the function foo) to a double? Can I prevent this and solve the problem? Do I use the std::variant class unidiomatic?


Solution

  • The issue here is that you query the current value stored at runtime, while the function signature of the template instantiation is performed at compile time. Consider how the member function looks like when you try using it to retrieve a double:

    double fun() {
        if (/* ... */)
            return std::get<double>(cont); // Ok return type is double, too
        else if (/* ... */)
            return std::get<std::string>(cont); // Error, return type should be string?!
    }
    

    This can't work. You need to change the way to access the data member, e.g. passing an overload set to std::visit, by providing two getter-like functions returning std::optional<double> and std::optional<std::string> or something similar.