Search code examples
c++templatesc++20variadic-templatesvariant

How to verify that two nested variants share the same type


The code uses nested std::variants to model messages:

class A{};
class B{};
class C{};
class D{};
using CMD1 = std::variant<A, B>;
using CMD2 = std::variant<C, D>;
using CMD = std::variant<cmd1, cmd2>;

In order to send the messages to the right actors the actors define a public typelist with all the types of the commands they want to receive:

struct ActorAC {
  using handleable = typelist<A,C>;
};
struct ActorBD {
  using handleable = typelist<B, D>;
};

At runtime the scheduler receives a command - the type is the variant encapsulating all of the other sub-variants, then it checks if the command's concrete type is inside the actors' typelist, if it is it sends the message to the actor:

CMD cmd = get_cmd();
//this is what I'd like to do
if (is_any(get_concrete_type(cmd), actorAC::handleables)){
  // send message to actorAC
}
if (is_any(get_concrete_type(cmd), actorBD::handleables)) {
  //send message to actor BD
}

getting the variant's current member is only possible with visit, so let's try that, we'll write a function that check if a given type is one of the concrete types of a variant:

template <typename HandlableType, typename... Types>
constexpr bool is_concrete(std::variant<Types...> v) {
  return std::visit(Overload{
                        [](HandelableType) {
                          /*it's the same Type! finished*/
                          return true;
                        },
                        []<typename... Typesin>(std::variant<Typesin...> vin) {
                          /* we still didn't get to the leaf variants*/
                          return is_in_concrete<HandlableType>(vin);
                        },
                        [](auto) {
                          /* if it is not a variant and also not our type - for sure it's not what we want*/
                          return false;
                        },
                    },
                    v);
}

let's try to use it:

int main(){
  CMD cmd(A());
  //should compile and return true
  return is_concrete<A>(cmd);
}

but I get this error from g++-13:

error: no matching function for call to 'is_concrete'

note: candidate template ignored: could not match 'std::variant<Types...>' against 'std::variant<V1, V2> ()(A)' (aka 'variant<variant<A, B>, variant<C, D>> ()(A)')

So at the first call from main the compiler doesn't match - what is happening??

ideally in the end it would be:

template<typename... tl>
bool shouldActUpon(CMD cmd) {
  return (is_concrete<tl>(cmd) || ...);
}

here is the complete file:

#include <variant>

template <typename... Ts> struct Overload : Ts... {
  using Ts::operator()...;
};
template <typename... Ts> Overload(Ts...) -> Overload<Ts...>;

class A {};
class B {};
class C {};
class D {};

using V1 = std::variant<A, B>;
using V2 = std::variant<C, D>;
using V = std::variant<V1, V2>;


template <typename HandlableType, typename... Types>
constexpr bool is_concrete(std::variant<Types...> cmd) {
  return std::visit(Overload{
                        [&](HandlableType) {
                          /*v1 is a variant in the index call recursive*/
                          return true;
                        },
                        [&]<typename... Typesin>(std::variant<Typesin...> vin) {
                          return is_concrete<HandlableType>(vin);
                        },
                        [](auto) {
                          /*v1 is not a variant - both are of the same type,
                           * same index, and leaf*/
                          return true;
                        },
                    },
                    cmd);
}

int main() {
  V cmd(A);
  is_concrete<A>(cmd);
}


Solution

  • If you expected main to return 1, try changing V cmd(A); to V cmd{A{}};.


    Indeed, if you look carefully at the error,

    note: candidate template ignored: could not match
    'std::variant<Types...>'
    against
    'std::variant<V1, V2> (*)(A)' (aka 'variant<variant<A, B>, variant<C, D>> (*)(A)')
    

    it's telling you that the compiler can't match the variant against... a pointer to function with argument of type A and return value the variant.

    So you should wonder "why in the world does the compiler think I'm passing a pointer to function"??

    Well, MVP is the answer: V cmd(A); is the declaration of a function named cmd with one argument of type A and return type V, which has type V(A); when passed around as an argument, that type decays to a pointer to funtion etc, i.e. V(*)(A).