Using tag-dispatch to implement or-combinable flags

Ros*_*ina 5 c++

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:

  • Multiple axis of variation
  • Use operator| to combine one mode from each axis
  • Different combinations of an intersecting set of axis of variations for different functions

I 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:

  • Can anyone suggest a better way to do this while retaining compile-time selection of all function variants? Better meaning: type-safe, less boiler-plate (all of sec 2. below is boiler plate), better error messages.
  • Can you recommend any related techniques?
  • The current method only supports combining two variation axis. It would be good to combine arbitrary axis into a tuple.
  • Update: A large part of the question relates to the implementation of 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)

谢谢!