[Boost::ext].SML:使用“_”(下划线)占位符时如何在 "on_entry<_>" 和 "on_exit<_>" 中获取 "real" 事件

[Boost::ext].SML: How to get the "real" event in "on_entry<_>" and "on_exit<_>" when using the '_' (underscore) placeholder

前提条件:
我使用 Compiler Explorer https://godbolt.org/ 来 运行 下面截取的代码。

问题描述:
当使用通用事件(“_”下划线)处理 on_entry/on_exit 定义时,我尝试访问事件成员。
这仅适用于编译器资源管理器上可用的最新 gcc 编译器 x86-64 gcc 11.1。对于旧版本,它会抛出错误。

所以我尝试了不同的解决方案来解决这个问题。

在我下面的代码片段中,您会发现 3 个不同的版本可用于测试此问题。
1. 和 2. 版本仅适用于最新的 gcc 编译器 x86-64 gcc 11.1
3. 版本允许用一个笨拙的 reinterpret_cast 来解决这个问题,我认为这不是一个好的解决方案。

问题:
A) 我目前使用 reinterpret_cast 的解决方案是安全的解决方案还是您看到了一些陷阱? 如果您看到一些陷阱,请解释它们。谢谢!

B) 是否有任何其他解决方案可以在不使用 reinterpret_cast 的情况下解决旧 gcc 编译器(如 x86-64 gcc 11.1)的这个问题? 谢谢!

截取的代码:

#include <https://raw.githubusercontent.com/boost-ext/sml/master/include/boost/sml.hpp>
#include <iostream>

namespace sml = boost::sml;
namespace {

struct BaseEvent {
    BaseEvent(int value = 200) : i(value) {}
    int i;
};

struct e1 : BaseEvent {};
struct e2 {};
struct e3 : BaseEvent { e3() : BaseEvent(300){} };

struct OnEntry{
    // Handle BaseEvent's and access members of that event
    void operator()(const auto& event){
        // 1. Version) Works only with compiler x86-64 gcc 11.1
        const int i = event.i;

        // 2. Version) Works only with compiler x86-64 gcc 11.1
        //const int i = (static_cast<const BaseEvent&>(event)).i;

        // 3. Version) Works with all compilers
        //const int i = (reinterpret_cast<const BaseEvent&>(event)).i;

        printf("[OnEntry] %s | BaseEvent related event with value=%d\n", sml::aux::get_type_name<decltype(event)>(), i);
    }

    // Used to handle non BaseEvent's
    void operator()(const e2& event){
        printf("[OnEntry] %s | other events\n", sml::aux::get_type_name<decltype(event)>());
    }
    
    void operator()(const sml::back::initial& event){}
};

struct TestSm {
    auto operator()() const noexcept {
        using namespace sml;
        return make_transition_table(
            *"idle"_s + event<e1> = "s1"_s,
            "s1"_s + event<e2> = "s2"_s,
            "s2"_s + event<e3> = "s3"_s,

            // The on_entry is defined to handle all events,
            // but OnEntry operator should handle the real event e1 provided from process_event.
            // This only works for compiler x86-64 gcc 11.1 for older versions
            // like x86-64 gcc 10.3 it won't work and requires an
            // awkward reinterpret_cast (see definition of OnEntry above).
            "s1"_s  + on_entry<_> / OnEntry(),
            "s2"_s  + on_entry<_> / OnEntry(),
            "s3"_s  + on_entry<_> / OnEntry()
        );
    }
};

}  // namespace

int main() {
    sml::sm<TestSm> sm;
    sm.process_event(e1{});
    sm.process_event(e2{});
    sm.process_event(e3{});
}

问题 A 的答案

如果删除以下重载,则会发生无效内存读取。 那是一个陷阱。

// Used to handle non BaseEvent's
void operator()(const e2& event){
    printf("[OnEntry] %s | other events\n", sml::aux::get_type_name<decltype(event)>());
}

问题 B 的答案

C++17

您可以使用 if constexpr 和 type_traits std::is_base_of_v 元函数解决问题。

这是一个解决方案代码:

void operator()(const auto& event){
    if constexpr (std::is_base_of_v<BaseEvent, std::remove_reference_t<decltype(event)>>) {
        const int i = event.i;
        printf("[OnEntry] %s | BaseEvent related event with value=%d\n", sml::aux::get_type_name<decltype(event)>(), i);
    }
}

我认为使用模板而不是 auto 参数类型更简单。 见以下代码:

template <typename T>
void operator()(const T& event){
    if constexpr (std::is_base_of_v<BaseEvent, T>) {
        const int i = event.i;
        printf("[OnEntry] %s | BaseEvent related event with value=%d\n", sml::aux::get_type_name<decltype(event)>(), i);
    }
}

我已经检查了我的解决方案是否适用于 gcc 7.3.0 及更高版本,以及 clang 3.9.1 或更高版本。

C++14

在 C++14 中,您需要用 SFINAE 替换 if constexpr。 所以代码如下:

// Handle BaseEvent's and access members of that event
template <typename T>
std::enable_if_t<std::is_base_of<BaseEvent, T>::value> 
operator()(const T& event){
    const int i = event.i;
    printf("[OnEntry] %s | BaseEvent related event with value=%d\n", sml::aux::get_type_name<decltype(event)>(), i);
}

// Used to handle non BaseEvent's
template <typename T>
std::enable_if_t<!std::is_base_of<BaseEvent, T>::value> 
operator()(const T& event){
    printf("[OnEntry] %s | other events\n", sml::aux::get_type_name<decltype(event)>());
}

注意

问题 B 的答案

根据 Takatoshi Kondo 的回答以及 c++14 标准没有使用 if-constexpr,我找到了另一个与 Takatoshi 非常相似的解决方案。

以下截取的代码必须替换原始问题中的整个 OnEntry 结构:

template <bool B>
struct Handle{ };

// Handle's BaseEvent and access members of that event
template<>
struct Handle<true>
{
    void operator()(const BaseEvent&  event)
    {
        const int i = event.i;
        printf("[OnEntry] %s | BaseEvent related event with value=%d\n", sml::aux::get_type_name<decltype(event)>(), i);
    }
};

// Handles non-BaseEvent's
template <>
struct Handle<false>
{
    template<class T>
    void operator()(const T&  event) {
        printf("[OnEntry] %s | non-BaseEvent related event\n", sml::aux::get_type_name<decltype(event)>());
    }
};

struct OnEntry{     
    template <class T>
    void operator()(const T& event){
         Handle<std::is_base_of<BaseEvent, T>::value>()( event );
    }
};

唯一让我不满意的是,由于编译器错误消息“错误:非命名空间范围内的显式特化”,我无法将此显式模板特化移动到 OnEntry 结构中。