Skip to content

Instantly share code, notes, and snippets.

@RossBencina
Last active December 5, 2016 15:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RossBencina/01148cea6125971b0fbea4b4752418b2 to your computer and use it in GitHub Desktop.
Save RossBencina/01148cea6125971b0fbea4b4752418b2 to your computer and use it in GitHub Desktop.
Using tag-dispatch to implement or-combinable flags. Code for my SO question: http://stackoverflow.com/questions/40977757
#include <iostream>
// **Current Solution: Part 1 - send() function**
// This is the easy part.
// First, a type-level enum helper
template<typename EnumClass, EnumClass X>
struct EnumValue {
using enum_class_type = EnumClass;
static constexpr enum_class_type value = X;
};
// Tag-based flag parameters:
enum class SendMode {
default_dispatch,
enqueue,
direct_deliver
};
constexpr EnumValue<SendMode, SendMode::default_dispatch> default_dispatch = {};
constexpr EnumValue<SendMode, SendMode::enqueue> enqueue = {};
constexpr EnumValue<SendMode, SendMode::direct_deliver> direct_deliver = {};
// `send()` uses tag-dispatch to specify send mode
void send(const char *s, decltype(default_dispatch)={})
{
std::cout << "send: default_dispatch " << s << "\n";
// specialized code for particular send mode goes here
}
void send(const char *s, decltype(enqueue))
{
std::cout << "send: enqueue " << s << "\n";
// specialized code for particular send mode goes here
}
void send(const char *s, decltype(direct_deliver))
{
std::cout << "send: direct_deliver " << s << "\n";
// specialized code for particular send mode goes here
}
// Excercise the code
void TEST_send()
{
send("0");
send("1", default_dispatch);
send("2", enqueue);
send("3", direct_deliver);
}
// ----------------------------------------------------------------------------------
// **Current Solution: Part 2 - mechanism for combing flags**
// We allow `|` to combine the two flags into an `EnumValuePair`
// This is a little limited (ideally we'd support combining more flags into tuples somehow).
template<typename EnumClass1, EnumClass1 X1, typename EnumClass2, EnumClass2 X2>
struct EnumValuePair {
static_assert(not std::is_same<EnumClass1, EnumClass2>::value, "Can't combine two enums of the same type.");
using first_type = EnumValue<EnumClass1, X1>;
using second_type = EnumValue<EnumClass2, X2>;
};
template<typename EnumClass1, EnumClass1 X1, typename EnumClass2, EnumClass2 X2>
constexpr auto operator|(EnumValue<EnumClass1, X1>, EnumValue<EnumClass2, X2>)
{
return EnumValuePair<EnumClass1, X1, EnumClass2, X2> {};
}
// Extraction mechanism. Allows us to extract individual flags from an expression
// that might be a single flag, or multiple flags or-ed together
// Given type `T`, extract an `EnumValueType` whose enum_class_type is `EnumClass`
// or use `Default` if no match is found. At the moment we match
// individual `EnumValue` instances, and `EnumValuePairs` (in future we could
// do the look up in tuples for example).
// Base case: `T` doesn't match `EnumClass`. use `Default`
template <typename EnumClass, typename T, typename Default>
struct get_enum_value {
using type = Default;
};
// specializations...
// Single value case, exact mach: T is an EnumValue<EnumClass, X>
template<typename EnumClass, EnumClass X, typename Default>
struct get_enum_value<EnumClass, EnumValue<EnumClass, X>, Default> {
using type = EnumValue<EnumClass, X>;
};
// Pair of values. first matches
template<typename EnumClass1, EnumClass1 X1, typename EnumClass2, EnumClass2 X2, typename Default>
struct get_enum_value<EnumClass1, EnumValuePair<EnumClass1, X1, EnumClass2, X2>, Default> {
using type = EnumValue<EnumClass1, X1>;
};
// second matches
template<typename EnumClass1, EnumClass1 X1, typename EnumClass2, EnumClass2 X2, typename Default>
struct get_enum_value<EnumClass2, EnumValuePair<EnumClass1, X1, EnumClass2, X2>, Default> {
using type = EnumValue<EnumClass2, X2>;
};
// Use `monostate` to represent the case where no flags are supplied.
struct monostate {};
// ----------------------------------------------------------------------------------
// **Current Solution: Part 3 - reply() function**
// This is similar to the implementation of `send()` except that we need to provide
// a way to extract two different modes from the single flag parameter.
enum class ReplyCompletionAction {
pre_complete,
complete,
post_complete
};
constexpr EnumValue<ReplyCompletionAction, ReplyCompletionAction::pre_complete> pre_complete = {};
constexpr EnumValue<ReplyCompletionAction, ReplyCompletionAction::complete> complete = {};
constexpr EnumValue<ReplyCompletionAction, ReplyCompletionAction::post_complete> post_complete = {};
// Implementation of the different variants of `reply()`.
// The structure of the example is that `reply()` invokes `send()`. We can
// capture `send()`'s tag via a template parameter and pass it through.
template<typename SendMode_>
void reply_(const char *s, SendMode_, decltype(pre_complete))
{
std::cout << "reply_: pre_complete " << s << "\n";
// specialized code for particular reply mode goes here
send(s, SendMode_{});
}
template<typename SendMode_>
void reply_(const char *s, SendMode_, decltype(complete))
{
std::cout << "reply_: complete " << s << "\n";
// specialized code for particular reply mode goes here
send(s, SendMode_{});
}
template<typename SendMode_>
void reply_(const char *s, SendMode_, decltype(post_complete))
{
std::cout << "reply_: post_complete " << s << "\n";
// specialized code for particular reply mode goes here
send(s, SendMode_{});
}
// Public `reply()` function:
// BUG: Any type at all could be passed as `ReplyFlags_` and it would be valid
// ideally we'd restrict ReplyFlags_ to only containing (at most) `SendMode` and `ReplyCompletionAction`
template<typename ReplyFlags_=monostate>
void reply(const char *s, ReplyFlags_={})
{
reply_(s,
typename get_enum_value<SendMode, ReplyFlags_, decltype(default_dispatch)>::type {},
typename get_enum_value<ReplyCompletionAction, ReplyFlags_, decltype(complete)>::type {});
}
// Test:
void TEST_reply()
{
reply("0"); // use default send mode and reply mode
reply("1", default_dispatch); // use default reply mode...
reply("2", enqueue);
reply("3", direct_deliver);
reply("4", pre_complete); // use default send mode...
reply("5", post_complete);
reply("6", complete);
reply("7", enqueue | post_complete); // send mode and reply mode
reply("8", direct_deliver | complete);
//reply("9", direct_deliver | enqueue); // correctly fails to compile, two send modes
//reply("10", post_complete | complete); // correctly fails to compile, two reply modes
}
// ----------------------------------------------------------------------------------
int main()
{
TEST_send();
TEST_reply();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment