Poperton
Poperton

Reputation: 2148

How to return a member that is defined in all alternatives of a std::variant, even if the members are different types?

Is it possible to get a variant's member called x even if it has a different type in each alternative?

#include <variant>

class A {
    int x = 0;
};

class B {
    int x = 1;
};

class C {
    bool x = false;
};

std::variant<A, B, C> variant = A{};

template<typename R> R getValue(std::variant<A, B, C>& variant ) {
    //return the value of x here
}

On Walk over variants that share the same member and get it?, I asked the same question, but it didn't specify that I wanted to get different types of values. This answer talks about std::visit() but that does not accept a lambda that returns different types.

Upvotes: 2

Views: 573

Answers (3)

Chris Uzdavinis
Chris Uzdavinis

Reputation: 6131

One more approach, again using visit. :) One thing to realize is that visit doesn't just take a single visitor, but can take a full overload set of visitor functions, allowing different functions to handle the different values. Thus you can flip the problem around and instead of trying to return one of the different values from your variant, you can provide handlers for the values and send the value to the proper handler.

For example, using the overloaded function-call operator in a struct:

struct A { int x = 0; };
struct B { std::string x = "2"; };
struct C { bool x = true; };

int main() {

    struct Process {
        void operator()(A const& a) const {
            cout << "Got an A: " << a.x << '\n';
        }
        void operator()(B const& b) const {
            cout << "Got a  B: " << b.x << '\n';
        }
        void operator()(C const& c) const {
            cout << "Got a  C: " << c.x << '\n';
        }
    };

    var = A{};
    std::visit(Process{}, var);

    var = B{};
    std::visit(Process{}, var);

    var = C{};
    std::visit(Process{}, var);
}

A "modern" popular alternate syntax utilizing a bunch of c++17 features (including CTAD, variadic templates, deduction guides, inheriting from lambdas, and variadic using declarations in just a few lines) also looks nice, and is approximately equivalent to the above, though the first 2 lines defining the overloaded class could be considered library code even if they do not make full sense yet.

// probably library code
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

struct A { int x = 0; };
struct B { std::string x = "2"; };
struct C { bool x = true; };

int main() {

    // and then we have this nice, concise syntax:

    auto process = [](auto & variant) {
        std::visit(
            overloaded{
                [](A const& a) { cout << "Got an A: " << a.x << '\n'; },
                [](B const& b) { cout << "Got a  B: " << b.x << '\n'; },
                [](C const& c) { cout << "Got a  C: " << c.x << '\n'; }
            },
            var);
    };

    var = A{};
    process(var);

    var = B{};
    process(var);

    var = C{};
    process(var);
}

Here's both versions together in Compiler Explorer: https://godbolt.org/z/8T91W4T9G

Upvotes: 2

Amir Kirsh
Amir Kirsh

Reputation: 13750

This is what you look for, I believe:

template<typename... Ts>
auto getValue(std::variant<Ts...>& v )
{
    using R = std::common_type_t<decltype(Ts::x)...>;
    return std::visit([](auto &obj){return static_cast<R>(obj.x);}, v);
}

Above code doesn't require you to state the type you want back but rather deduces it by using std::common_type of the variable x in all the different possible values in your variant.

Code: http://coliru.stacked-crooked.com/a/a1f54902ea1a9db3

Upvotes: 2

Ranoiaetep
Ranoiaetep

Reputation: 6637

With the signature and template of your getValue function, you would actually need to manually decide the type of R when calling it. So in main, you would call it like:

std::cout << getValue<int>(variant); // prints 0;

And if that is what you intended, then you can simply cast the .x to type R:

template<typename R>
R getValue(std::variant<A, B, C>& v )
{
    return std::visit([](auto &obj){return R(obj.x);}, v);
}

However, I highly doubt that's what you intended.

Upvotes: 1

Related Questions