The code uses nested std::variant
s 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);
}
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)
.