1

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)?

  • 5
    Unfortunately, C++ does not work this way. The return value of all functions must be specified at compile time. It cannot vary at runtime. This is fundamental to C++, and there are no workarounds or exceptions. About the only thing you can do is create a wrapper object with an operator overload that calls `std::get` on the underlying returned variant. – Sam Varshavchik Feb 17 '23 at 22:18
  • Maybe Unions could help you out – MichiBros Feb 17 '23 at 22:35
  • 3
    @MichiBros `std::variant` is already an union, but typesafe. There's nothing to gain from replacing `variant` with `union` here. – Yksisarvinen Feb 17 '23 at 22:42
  • What if you write `B b = m.internalMap[1];`? – user253751 Feb 17 '23 at 23:51

2 Answers2

3
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

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • 1
    @TheMemeMachine I have fixed the syntactical issues. The C++ does indeed allow overloading functions that defer only in return type, but ONLY for type-conversion operators, which this is using. The `using` statement has been fixed, that causes the class to inherit the constructors. – Mooing Duck Feb 21 '23 at 16:07
  • 2
    @TedLyngmo: At the time of his comment, the code didn't compile. I'd fixed the code after his comment. Oh, you're hinting that he should check the green arrow to the left below the vote buttons to mark that this was the answer? Yes, that would be nice. – Mooing Duck Jun 01 '23 at 20:12
1

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.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • It's not *quite* impossible. The values in the map could be a type that wraps the variant, which also overloads `operator A&()` and `operator B&()`, and then this would work fine. – Mooing Duck Feb 17 '23 at 22:46
  • @MooingDuck I am referring to the comment `I want the type of internalMap[1] to be A, and type of internalMap[2] to be B` in OP's code. – user17732522 Feb 17 '23 at 22:47