Skip to content

Instantly share code, notes, and snippets.

@anthonygclark
Created May 2, 2017 22:28
Show Gist options
  • Save anthonygclark/6e1d5df3e95fc600a7948b5340b8e43b to your computer and use it in GitHub Desktop.
Save anthonygclark/6e1d5df3e95fc600a7948b5340b8e43b to your computer and use it in GitHub Desktop.
std::queue with boost::variant and dispatch type handling
#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