boostorg / mp11

C++11 metaprogramming library
http://boost.org/libs/mp11
244 stars 66 forks source link

boost::mp11::mp_list can't be used as base class for transition table within msm::front::state_machine_def #63

Open PiotrNycz opened 3 years ago

PiotrNycz commented 3 years ago

I just wanted to modernize my code - and try to use boost::mp11::mp_list instead of boost::mpl::vector in my FSM definitions.

Simplified code is as follows:

#include <iostream>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>

#include <boost/mp11/list.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/mpl.hpp>

namespace msm = boost::msm;

namespace test_fsm 
{
struct play {};
struct stop {};
struct player_ : public msm::front::state_machine_def<player_>
{
    typedef int no_exception_thrown;
    typedef int no_message_queue;

    struct Empty : public msm::front::state<> 
    {
        template <class Event,class FSM>   void on_entry(Event const&,FSM& ) { std::cout << "entering: Empty" << std::endl; }
        template <class Event,class FSM>   void on_exit(Event const&,FSM& ) { std::cout << "leaving: Empty" << std::endl; }
    };
    struct Playing : public msm::front::state<>
    {
        template <class Event,class FSM>    void on_entry(Event const&,FSM& ) { std::cout << "entering: Playing" << std::endl; }
        template <class Event,class FSM>    void on_exit(Event const&,FSM& ) { std::cout << "leaving: Playing" << std::endl; }
    };

    typedef Empty initial_state;

    // transition actions
    void playing(play const&)  {  }
    void stop_playing(stop const&)  {  }

    typedef player_ p; 

    struct transition_table : boost::mp11::mp_list<
            _row < Empty , play        , Playing       >,
            _row < Playing , stop        , Empty       >
    > {};

    // Replaces the default no-transition response.
    template <class FSM,class Event>
    void no_transition(Event const& e, FSM&,int state)
    {
        std::cout << "no transition from state " << state
            << " on event " << typeid(e).name() << std::endl;
    }

    typedef msm::back::state_machine<player_> player;

    //
    // Testing utilities.
   //
   static char const* const state_names[] = { "Empty", "Playing" };

    void pstate(player const& p)
   {
       std::cout << " -> " << state_names[p.current_state()[0]] << std::endl;
   }
};

}

int main()
{
    test_fsm::player p2;
    p2.start();
    p2.process_event(test_fsm::play());
    p2.process_event(test_fsm::stop()); 
    return 0;
}

It compiles, but produces unexpected output:

entering: Empty
no transition from state 0 on event N8test_fsm4playE
no transition from state 0 on event N8test_fsm4stopE

While - with original version (just replacing mp11::mp_list with mpl::vector) it works as expected:

entering: Empty
leaving: Empty
entering: Playing
leaving: Playing
entering: Empty

It also works as expected with boost::fusion::vector.

I found one workaround - when using type-alias for defining transition table it works as expected:

    using transition_table = boost::mp11::mp_list<
            _row < Empty , play        , Playing       >,
            _row < Playing , stop        , Empty       >
    > ;

With type-alias it also works for other types boost::mpl::vector and boost::fustion::vector.

I am not sure what this inheritance is for, but all examples from boost::msm defines this transition tables that way.

The problem with boost::mp11::mp_list is probably here <boost/mp11/mpl.hpp> - but I am not sure that.

I asked the question about that on SO: https://stackoverflow.com/questions/68195912/boostmp11mp-list-cant-define-proper-transition-table-for-fsm-based-on-boost - but not get much attention.

You can play with the problem on compiler explorer: https://godbolt.org/z/jTEnxPMTj

The problem is not related to version of boost or compiler.

pdimov commented 3 years ago

While types derived from mpl::vector<> and fusion::vector<> are also MPL sequences (whether accidentally or by design, I'm not quite sure), types derived from mp_list aren't. See f.ex. https://godbolt.org/z/71sGzP4zj. Only mp_list<> itself is an MPL sequence. So using (or typedef) is correct.

I also don't know why the MSM examples use inheritance.

PiotrNycz commented 3 years ago

It seems that boost::mp11:mp_list is simple type defined as:

template <typename ...T> struct mp_list {};

while both fusion and mpl vectors are types referring to itself - in mp_list it could be something as:

template <typename ...T> struct mp_list {
   using mp_list_type = mp_list;
};

thus it would be possible to define mpl adapters working also with types derived from type-list type.

I also found in fusion-mpl adapters, that it is also required some inner-type for tag used in mpl adapters, in our example something like this:

struct mp_list_mpl_tag;
template <typename ...T> struct mp_list {
   using mp_list_type = mp_list;
   using mp_list_tag = ::boost::mp11::mp_list_mpl_tag;
};

I also tried at build-bench.com whether inheritance is somehow faster in compilation - and it is really faster but almost impossible to notice the difference, it is so small - see https://build-bench.com/b/kslOjZyqZVmCm6qrMEgf1ptanbM

namespace some
{
struct type_list_tag;

template <typename ...T>
struct type_list
{
    using type = type_list;
    using tag = type_list_tag;
};

template <typename T>
struct front_impl;

template <template <class...> class TL, typename T1, typename ...T>
struct front_impl<TL<T1, T...>>
{
    using type = T1;
};

template <typename T>
struct front
{
    using type = typename front_impl<typename T::type>::type;
};

template <typename T>
using front_t = typename front<T>::type;

template <typename T>
struct pop_front_impl;

template <template <class...> class TL, typename T1, typename ...T>
struct pop_front_impl<TL<T1, T...>>
{
    using type = TL<T...>;
};

template <typename T>
struct pop_front
{
    using type = typename pop_front_impl<typename T::type>::type;
};

template <typename T>
using pop_front_t = typename pop_front<T>::type;

}

#if USE_INHERITANCE
struct test : some::type_list<int,float,int,float,int,float> {};
#else
using test = some::type_list<int,float,int,float,int,float>;
#endif

int main() {
    using namespace some;
    front_t<pop_front_t<pop_front_t<test>>> a = 5;
    front_t<pop_front_t<pop_front_t<pop_front_t<test>>>> b = 4;

    return a + b;
}
PiotrNycz commented 2 years ago

Maybe the best solution to this problem would be to create boost::mp11::mp_list2 just to be used (with inheritance) in boost::msm transition table. PoC is here https://wandbox.org/permlink/ciX5jWNdWTPLnGVF

The idea in short:

namespace boost::mp11 {

namespace aux {  struct mp11_tag2; }

template <typename ...T>
struct mp_list2 
{ 
     using tag = aux::mp11_tag2; // mpl will catch this nested tag (see boost::mpl::sequence_tag generic impl)
     using base = mp_list<T...>; // this will be accessible from derived classes

      // the below line is not really needed - we can use aux::mp11_tag instead
      // I just added it -- in case someone invents idea to have different tags for different type packs
      using base_tag = typename ::boost::mpl::sequence_tag<base>::type;

};
}

And just forward all mpl operations/algorithms to base mp_list

namespace boost::mpl
{
// at

template<> struct at_impl<::boost::mp11::aux::mp11_tag2>
{
    template<class L, class I> 
    struct apply 
    {
        using type = typename at_impl<typename L::base_tag>
            ::template apply<typename L::base, I>
            ::type;
    };
};

// back
//...