Implementation of a polymorphic [] operator for accessing a std::variant

151 Views Asked by At

Suppose I have a struct MyMap, which is a wrapper for std::map where value type is std::variant<A, B> but A and B behave completely different (they have different member functions and fields, and share no common functionality).

For example:

#include <map>
#include <variant>
#include <string>

struct A {
   std::string val;
};

struct B {
   int val;
};

struct MyMap {
   std::map<int, std::variant<A, B>> internalMap;
};

I want to implement an operator[] overload for MyMap such that the type of the accesed element is the actual type A or B.

To clarify further, i DO NOT want this: std::variant<A, B> operator[](int key) { return internalMap[key]; } , but I want something like:

// If I have:

MyMap m;
m.internalMap[1] = A{};
m.internalMap[2] = B{};

// I want the type of internalMap[1] to be A, and type of internalMap[2] to be B:
A a = m.internalMap[1];
B b = m.internalMap[2];

I want to be able to call methods specific to type A or type B after accessing them with this operator[] overload.

Should this type deduction happen at runtime or at compile time (and how)?

2

There are 2 best solutions below

2
Mooing Duck On BEST ANSWER
struct AorB : public std::variant<A, B> {
    using std::variant<A, B>::variant;

    operator A&() {return std::get<A>(*this);}
    operator const A&() const {return std::get<A>(*this);}
    operator B&() {return std::get<B>(*this);}
    operator const B&() const {return std::get<B>(*this);}
};

And then you store this as the value of the map. Then, you can pass this to methods expecting an A or a B, and it works, as long as it's unambiguous.

A a = m.internalMap[1]; //no problem!
B b = m.internalMap[2];

Proof of execution

2
user17732522 On

I want the type of internalMap[1] to be A, and type of internalMap[2] to be B

That's impossible. What type the expression m.internalMap[i] has is complete determined by the types and value categories of m and i and the mapping is determined completely at compile-time. C++ is a statically-typed language. (There is one exception though: Integer literals with value 0 have additional conversion rules, so they can result in different types of the expression.)

Both 1 and 2 are of type int and have same value category (prvalue). So the type of m.internalMap[1] and m.internalMap[2] must be the same.

You can only have differing types as outcome if you use different types as input, for example you can use user-defined literals to get a syntax like

m.internalMap[1_c]

or if you use the index as a template argument instead of a function argument / expression operand:

m.internalMap.someFunction<1>();

In either case this works only if the index is a compile-time constant and it isn't really useful, because you would need to know at compile-time which type is stored at which index, in which case using std::variant in the first place would be unnecessary.

To access elements of the std::variant in the map use std::get or std::visit with a generic lambda that can choose an action based on the type. If there is a type you don't expect at the index you are looking at you'll have to throw an exception or handle the situation in some other way. (std::get will throw for you if you use the wrong type.)

A a = std::get<A>(m.internalMap[1]);
B b = std::get<B>(m.internalMap[2]);

This will throw std::bad_variant_access if there isn't a A at index 1 and a B at index 2.

Also, just because internalMap[1] and internalMap[2] have the same type doesn't mean that the syntax

A a = m.internalMap[1];
B b = m.internalMap[2];

can't work. For example A and B could have converting constructors from std::variant<A, B> or std::variant<A,B> could be replaced by a wrapper type around the variant with conversion operators to A and B (see other answer). In either case, the type mismatch situation would still need to be handled at runtime.