I would like to use tag dispatch to implement a kind of "mode select" parameter to select among multiple implementations of a function. This is easy when there is only one function, and one axis of variation. However, I would like to implement something more flexible supporting:
operator| to combine one mode from each axisI will give a working example below. Here is a quick preview of what I mean:
A function send() has a SendMode selector, which is one of { default_dispatch, enqueue, direct_deliver }. You can write something like:
send("text"); // uses the default SendMode tag
send("text", default_dispatch);
send("text", enqueue);
send("text", direct_deliver);
Run Code Online (Sandbox Code Playgroud)
send has one axis of variation: the SendMode;
A function reply() can take a SendMode, but also a ReplyCompletionAction selector, which is one of { pre_complete, complete, post_complete }. You can write things like:
reply("text"); // use default SendMode and ReplyCompletionAction
reply("text", default_dispatch); // use default reply ReplyCompletionAction
reply("text", pre_complete); // use default SendMode
reply("text", enqueue | post_complete); // combine both with |
Run Code Online (Sandbox Code Playgroud)
reply has two axes of variation: SendMode and ReplyCompletionAction. At most, one of each can be supplied, and combined with |.
For full context, I will give my current solution below. But first, here are my questions:
reply() below. How best can it be made both succinct, and express exactly the types that it accepts.Thanks!
The code below compiles and runs. I have provided the full listing in a gist here: https://gist.github.com/RossBencina/01148cea6125971b0fbea4b4752418b2
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;
};
Run Code Online (Sandbox Code Playgroud)
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 = {};
Run Code Online (Sandbox Code Playgroud)
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
}
Run Code Online (Sandbox Code Playgroud)
Excercise the code
void TEST_send()
{
send("0");
send("1", default_dispatch);
send("2", enqueue);
send("3", direct_deliver);
}
Run Code Online (Sandbox Code Playgroud)
Current Solution: Part 2 - mechanism for combing flags
We allow | to combine the two flags into an EnumValuePair
这是有一点限制的(理想情况下我们支持以某种方式将更多标志组合到元组中)。
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> {};
}
Run Code Online (Sandbox Code Playgroud)
提取机制。允许我们从表达式中提取单独的标志,该表达式可能是单个标志,也可能是多个标志或一起
给定 type T,提取EnumValueType其 enum_class_type 为的类型,或者如果未找到匹配项则EnumClass
使用。Default目前我们匹配单个EnumValue实例,并且EnumValuePairs(例如,将来我们可以在元组中进行查找)。
// 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 {};
Run Code Online (Sandbox Code Playgroud)
当前解决方案:第 3 部分 -reply() 函数
这与 的实现类似,send()只是我们需要提供一种从单个标志参数中提取两种不同模式的方法。
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 = {};
Run Code Online (Sandbox Code Playgroud)
的不同变体的实现reply()。该示例的结构是reply()调用send(). 我们可以send()通过模板参数捕获 的标签并将其传递。
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_{});
}
Run Code Online (Sandbox Code Playgroud)
公共reply()职能:
// 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 {});
}
Run Code Online (Sandbox Code Playgroud)
测试:
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();
}
Run Code Online (Sandbox Code Playgroud)
谢谢!