Last active
December 5, 2016 15:51
-
-
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
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> | |
// **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