Home > front end >  Extracting the value of a variant to a superclass
Extracting the value of a variant to a superclass

Time:09-28

Consider the following classes

class A {
    public:
    virtual std::string to_string() = 0;
};

class B : public A {
    public:
    std::string to_string(){
        return "B";
    }
};

class C : public A {
    public:
    std::string to_string(){
        return "C";
    }
};
 
class D {};

Now I will have a variant std::variant<B*, C*, D*> bcd;The variable can of course, depending on the user input, hold either a variable of type B* or of type C*. At a given time in the program, I want to extract the value to pass to a method taking the superclass A* as an argument. Now, if I do this explicitly like this:

bc = new C();
A* a = std::get<C*>(bc);

It works as expected. But I do not know at this point, which type the inner value of bc will have.

I have tried the following:

  • Adding A* to the variant and trying to access it with std::get<A*>(bc), which results in a bad variant access.

  • Creating a visitor pattern to return the type (even though this seems cumbersome for this simple task) like this

    class Visitor {
      template<typename TType>
      TType operator()(TType arg) {
        return arg;
      }
    };
    A* a2 = std::visit(visitor, bc);
    

    which produces a no type named ‘type’ in ‘std::conditional_t‘. (I also tried it without templates).

Is there a right / elegant way to do this without having to do something like

 if(B* b = std::get_if<B*>(bc))

for every type that I have?

CodePudding user response:

You were close with std::visit, not sure what the error is exactly but I would recommend using a lambda instead of a custom struct:

int main(){
    std::variant<B*, C*> bc;
    bc = new C();
    A* a = std::visit([](auto&& e)->A*{return e;},bc);
    a->to_string();
}

The above will compile iff all variants can be casted to A* and is thus safe.

If some of variants are not derived from A*, you could use this longer version:

    A* a = std::visit(
        [](auto& e) -> A* {
            if constexpr (std::is_convertible_v<decltype(e),A*>)
                return e;
            else
                return nullptr;
        },
        bc);

It will return nullptr if the currently held type cannot be casted to A.

One can hide the ugly lambda (the simple one above can be hidden too) into a global variable template, full example:

template <typename T>
constexpr auto shared_cast = [](auto& e) -> T* {
    if constexpr (std::is_convertible_v<decltype(e), T*>)
        return e;
    else
        return nullptr;
};

int main() {
    std::variant<B*, C*, D*> bc;
    bc = new C();
    A* a = std::visit(shared_cast<A>, bc);

    if (a != nullptr) a->to_string();
}

Feel free to refactor the whole std::visit expression into a template function:

template <typename T,typename V>
T* visit2(V& variant){
    return std::visit(shared_cast<T>,variant);
}
  • Related