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?

2 Answers

3
lubgr On Best Solutions

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.

2
Sombrero Chicken On

All runtime if branches must be compilable even if not taken. If we call fun() with V == double then returning an std::string makes no sense and causes the error (even if that branch would never be taken, the compiler can't know that for certain).

Instead, just return it right away through V:

template <typename V> V fun() {
    if (std::holds_alternative<V>(cont))
        return std::get<V>(cont);
    return {}; // return default constructed V. You could throw an exception here instead etc.
}