How do you make the use of std::variants more "palatable", syntax-wise?

Viewed 182

This is motivated by an answer I gave a newbie user, where I suggested they use an std::variant instead of a union.

With a union, you may have something like the following:

struct Box {
    struct Item { float value; };
    using Boxes = std::vector<Box>;

    union Value {
        Item item;
        Boxes boxes;
    };

    Value contents;
    std::string label;
};

(not exactly what the original question had, I'm taking some poetic license here.) and with a variant, the class might look like this:

struct Box {
    struct Item { float value; };
    using Boxes = std::vector<Box>;

    std::variant<Item, Boxes> contents;
    std::string label;
};

The thing is, that with the first variant, I can write

if (box.contents.boxes.size() > 2) { foo(); }

and provided that I've already established there are going to be sub-boxes, this will work.

With an std::variant, I have to write:

if (std::get<Boxes>(box.contents).size() > 2) { foo(); }

I feel the second version is much less readable, a bit confusing, and quite distracting. Plus - I have to know the type of boxes.

What can I do, in my code, to spare my users the need to make this kind of std::get() calls, and make their lives more pleasant?

2 Answers

Just add some accessors wrapping std::gets:

struct Box {
    struct Item { float value; };
    using Boxes = std::vector<Box>;

    std::variant<Item, Boxes> contents;
    std::string label;

    decltype(auto) item()       { return std::get<Item>(contents); }
    decltype(auto) item() const { return std::get<Item>(contents); }

    decltype(auto) boxes()       { return std::get<Boxes>(contents); }
    decltype(auto) boxes() const { return std::get<Boxes>(contents); }
};

And then it goes:

if (box.boxes().size() > 2) { foo(); }

How about "visiting" methods? Something like this:

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


struct Box {
    struct Item { float value; };
    using Boxes = std::vector<Box>;

    std::variant<Item, Boxes> contents;
    std::string label;

    decltype(auto) size() const {
        return std::visit(overloaded {
            [](const Item&)        { return 1; }
            [](const Boxes& boxes) { return boxes.size(); } // non-recursive
        }, *this);
    }
};

and then you write:

if (box.size() > 2 ) { foo(); }

?

Related