c++booststate-machineboost-sml

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


Precondition:
I used the Compiler Explorer https://godbolt.org/ to run the code snipped below.

Problem description:
I tried to access the events members when using the generic event ('_' underscore) handling for on_entry/on_exit definitions.
This is working only for the newest gcc compiler x86-64 gcc 11.1 available on the Compiler Explorer. For older versions it throws errors.

So I tried different solutions to overcome this issue.

In my code snipped below you will find 3 different versions that can be used to test this issue.
The 1. and the 2. Version will only work with the newest gcc compiler x86-64 gcc 11.1
The 3. Version allows to solve this issue with an awkward reinterpret_cast which I think is not a nice solution.

Questions:
A) Is my current solution with reinterpret_cast a safe solution or do you see some pitfalls? If you see some pitfalls, then please explain them. Thanks!

B) Is there any other solution to overcome this issue with older gcc compilers as the x86-64 gcc 11.1 without using a reinterpret_cast? Thanks!

Code snipped:

#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{});
}

Solution

  • Answer for question A

    If you remove the following overload, then invalid memory read would happen. That is a pitfall.

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

    Answer for question B

    C++17

    You can solve the problem using if constexpr and type_traits std::is_base_of_v meta function.

    This is a solution code:

    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);
        }
    }
    

    I think that using template instead of auto parameter type is simpler. See the following code:

    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);
        }
    }
    

    I've checked my solution work with gcc 7.3.0 and later, and clang 3.9.1 or later.

    C++14

    In C++14, you need to replace if constexpr wifh SFINAE. So the code is as follows:

    // 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)>());
    }
    

    NOTE