Created
May 2, 2017 22:28
-
-
Save anthonygclark/6e1d5df3e95fc600a7948b5340b8e43b to your computer and use it in GitHub Desktop.
std::queue with boost::variant and dispatch type handling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <queue> | |
#include <boost/variant.hpp> | |
struct message_tag { | |
bool handled = false; | |
message_tag() = default; | |
message_tag(std::nullptr_t) { } | |
}; | |
/* some types... */ | |
struct message_type1 : message_tag { }; | |
struct message_type2 : message_tag { }; | |
struct message_type3 : message_tag { }; | |
/** | |
* @brief Simple wrapper for boost::static_visitor<> | |
* @details Provides CRTP callback into operator()(H) | |
* @tparam C The CRTP type in which will handle the operator()s | |
* @tparam R The return type of apply. See how message_queue_handler_pointers uses it | |
*/ | |
template<typename C, typename R> | |
struct queue_dispatcher : boost::static_visitor<R> | |
{ | |
/* @details CRTP dispatch */ | |
template<typename H> | |
H operator() (H & h) { | |
return static_cast<C *>(this)->operator()(h); | |
} | |
/** | |
* @brief Wrapper for boost::apply_visitor | |
*/ | |
template<typename V> | |
R apply(V & v) { | |
return boost::apply_visitor(*this, v); | |
} | |
}; | |
/** | |
* @brief Queue of boost::variant | |
* @details Always includes nullptr as a terminator | |
* @tparam Ts Types for the variant queue | |
*/ | |
template<typename... Ts> | |
using variant_queue = std::queue<boost::variant<std::nullptr_t, Ts...>>; | |
/** | |
* @brief Interface for handling queue types | |
* @details Note that the default impl will simply return the value. | |
* @note The return types here are points since I know the queue contains pointers... | |
* @note These return nullptr instread of throw since I want to know if any subclass of | |
* message_queue_handler_pointers EVER handles these. In some cases, I want nullptr to be a terminator | |
* for a queue (ie - shutdown), or I want to know when noone has handled a message. | |
*/ | |
struct message_queue_handler_pointers : queue_dispatcher<message_queue_handler_pointers, message_tag * /* Would be nice to detect this ... TODO */> | |
{ | |
virtual message_type1 * operator() (message_type1 *& f) { return nullptr; } | |
virtual message_type2 * operator() (message_type2 *& f) { return nullptr; } | |
virtual message_type3 * operator() (message_type3 *& f) { return nullptr; } | |
std::nullptr_t operator() (std::nullptr_t &) { return nullptr; } | |
}; | |
/** | |
* @brief Same as above but with a value type... | |
*/ | |
struct message_queue_handler_values : queue_dispatcher<message_queue_handler_values, message_tag /* Would be nice to detect this ... TODO */> | |
{ | |
virtual message_type1 operator() (message_type1 & f) { return { }; } | |
virtual message_type2 operator() (message_type2 & f) { return { }; } | |
virtual message_type3 operator() (message_type3 & f) { return { }; } | |
std::nullptr_t operator() (std::nullptr_t &) { return nullptr; } | |
}; | |
void test_with_pointers() | |
{ | |
struct queue_sink1 : message_queue_handler_pointers | |
{ | |
// overridden call to handle type message_type1 | |
message_type1 * operator() (message_type1 *& f) override { | |
std::cout << "WOO HOO\n"; | |
return f; | |
} | |
}; | |
struct queue_sink2 : message_queue_handler_pointers | |
{ | |
// overridden call to handle type message_type2 | |
message_type2 * operator() (message_type2 *& f) override { | |
std::cout << "YEE HEE\n"; | |
return f; | |
} | |
}; | |
/* Create a queue that can hold all of your types ... */ | |
variant_queue<message_type1*, message_type2*, message_type3*> queue; | |
/* Create some handler instances */ | |
queue_sink1 qs1; | |
queue_sink2 qs2; | |
/* Put those in a list ... */ | |
std::vector<message_queue_handler_pointers *> qh{&qs1, &qs2}; | |
/* Create some messages */ | |
message_type1 f; | |
message_type2 ff; | |
message_type3 fff; | |
/* Put them on the queue */ | |
queue.push(&f); | |
queue.push(&ff); | |
queue.push(&fff); | |
queue.push(nullptr); | |
/* Peek at the queue's front message and apply | |
* it on all queue_sinks/queue_handlers. If none of the handlers handle it, | |
* or nullptr is received, break the loop | |
*/ | |
while (queue.size() > 0) | |
{ | |
auto p = queue.front(); | |
if (std::all_of(qh.begin(), qh.end(), | |
[&p](decltype(qh)::value_type v) -> bool { | |
return (v->apply(p) == nullptr); // check nullptr since variant contains pointers | |
})) | |
{ | |
std::cout << "Breaking out of queue loop since no one handled p\n"; | |
break; | |
} | |
queue.pop(); | |
} | |
std::cout << queue.size() << " items left in queue\n"; | |
} | |
void test_with_values() | |
{ | |
struct queue_sink1 : message_queue_handler_values | |
{ | |
// overridden call to handle type message_type1 | |
message_type1 operator() (message_type1 & f) override { | |
std::cout << "WOO HOO\n"; | |
f.handled = true; | |
return f; | |
} | |
}; | |
struct queue_sink2 : message_queue_handler_values | |
{ | |
// overridden call to handle type message_type2 | |
message_type2 operator() (message_type2 & f) override { | |
std::cout << "YEE HEE\n"; | |
f.handled = true; | |
return f; | |
} | |
}; | |
/* Create a queue that can hold all of your types ... */ | |
variant_queue<message_type1, message_type2, message_type3> queue; | |
/* Create some handler instances */ | |
queue_sink1 qs1; | |
queue_sink2 qs2; | |
/* Put those in a list ... */ | |
std::vector<message_queue_handler_values *> qh{&qs1, &qs2}; | |
/* Create some messages */ | |
message_type1 f; | |
message_type2 ff; | |
message_type3 fff; | |
/* Put them on the queue */ | |
queue.push(f); | |
queue.push(ff); | |
queue.push(fff); | |
queue.push(nullptr); | |
/* Peek at the queue's front message and apply | |
* it on all queue_sinks/queue_handlers. If none of the handlers handle it, | |
* or nullptr is received, break the loop | |
*/ | |
while (queue.size() > 0) | |
{ | |
auto p = queue.front(); | |
if (std::all_of(qh.begin(), qh.end(), | |
[&p](decltype(qh)::value_type v) -> bool { | |
auto x = v->apply(p); | |
return (x.handled == false); // check sentinel.. TODO | |
}) | |
) | |
{ | |
std::cout << "Breaking out of queue loop since no one handled p\n"; | |
break; | |
} | |
queue.pop(); | |
} | |
std::cout << queue.size() << " items left in queue\n"; | |
} | |
int main(void) | |
{ | |
test_with_pointers(); | |
std::cout << "------\n"; | |
test_with_values(); | |
} | |
/**** OUTPUT | |
**** | |
**** | |
WOO HOO | |
YEE HEE | |
Breaking out of queue loop since no one handled p | |
2 items left in queue | |
------ | |
WOO HOO | |
YEE HEE | |
Breaking out of queue loop since no one handled p | |
2 items left in queue | |
*/ | |
/**** Valgrind | |
**** | |
**** | |
==17017== Memcheck, a memory error detector | |
==17017== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. | |
==17017== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info | |
==17017== Command: ./a.out | |
==17017== | |
WOO HOO | |
YEE HEE | |
Breaking out of queue loop since no one handled p | |
2 items left in queue | |
------ | |
WOO HOO | |
YEE HEE | |
Breaking out of queue loop since no one handled p | |
2 items left in queue | |
==17017== | |
==17017== HEAP SUMMARY: | |
==17017== in use at exit: 0 bytes in 0 blocks | |
==17017== total heap usage: 12 allocs, 12 frees, 79,136 bytes allocated | |
==17017== | |
==17017== All heap blocks were freed -- no leaks are possible | |
==17017== | |
==17017== For counts of detected and suppressed errors, rerun with: -v | |
==17017== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment