Named, static dispatching with std::variant












7














I need to fill in some template magic to make the following code snippet to work.



The problem is that I want to be able to define a visitor class for std::variant with named static methods accepting two arguments. How can I fill in Applicator::apply() to make the dispatching work?



struct EventA {};

struct EventB {};

struct EventC {};

using Event = std::variant<EventA, EventB, EventC>;

struct Visitor {
enum class LastEvent { None, A, B, C };

struct State {
LastEvent last_event = LastEvent::None;
};

static State apply(State s, EventA e) { return State{LastEvent::A}; }

static State apply(State s, EventB e) { return State{LastEvent::B}; }
};

template <typename Visitor> struct Applicator {

static State apply(State s, Event e) {

/*** Start of pseudo code ***/
if (Visitor can apply) {
return Visitor::apply(s, e);
}
/*** End of pseudo code ***/

// Else, don't update state state
return s;
}
};

int main() {
// Handled by visitor
State s1 = Applicator<Visitor>::apply(State{}, EventA{});
assert(s1.last_event == Visitor::LastEvent::A);

// Handled by visitor
State s2 = Applicator<Visitor>::apply(State{}, EventB{});
assert(s2.last_event == Visitor::LastEvent::B);

// NOT handled by visitor
State s3 = Applicator<Visitor>::apply(State{}, EventC{});
assert(s3.last_event == Visitor::LastEvent::None);
}









share|improve this question
























  • Aside: is State really meant to be a member of Visitor?
    – Caleth
    6 secs ago
















7














I need to fill in some template magic to make the following code snippet to work.



The problem is that I want to be able to define a visitor class for std::variant with named static methods accepting two arguments. How can I fill in Applicator::apply() to make the dispatching work?



struct EventA {};

struct EventB {};

struct EventC {};

using Event = std::variant<EventA, EventB, EventC>;

struct Visitor {
enum class LastEvent { None, A, B, C };

struct State {
LastEvent last_event = LastEvent::None;
};

static State apply(State s, EventA e) { return State{LastEvent::A}; }

static State apply(State s, EventB e) { return State{LastEvent::B}; }
};

template <typename Visitor> struct Applicator {

static State apply(State s, Event e) {

/*** Start of pseudo code ***/
if (Visitor can apply) {
return Visitor::apply(s, e);
}
/*** End of pseudo code ***/

// Else, don't update state state
return s;
}
};

int main() {
// Handled by visitor
State s1 = Applicator<Visitor>::apply(State{}, EventA{});
assert(s1.last_event == Visitor::LastEvent::A);

// Handled by visitor
State s2 = Applicator<Visitor>::apply(State{}, EventB{});
assert(s2.last_event == Visitor::LastEvent::B);

// NOT handled by visitor
State s3 = Applicator<Visitor>::apply(State{}, EventC{});
assert(s3.last_event == Visitor::LastEvent::None);
}









share|improve this question
























  • Aside: is State really meant to be a member of Visitor?
    – Caleth
    6 secs ago














7












7








7







I need to fill in some template magic to make the following code snippet to work.



The problem is that I want to be able to define a visitor class for std::variant with named static methods accepting two arguments. How can I fill in Applicator::apply() to make the dispatching work?



struct EventA {};

struct EventB {};

struct EventC {};

using Event = std::variant<EventA, EventB, EventC>;

struct Visitor {
enum class LastEvent { None, A, B, C };

struct State {
LastEvent last_event = LastEvent::None;
};

static State apply(State s, EventA e) { return State{LastEvent::A}; }

static State apply(State s, EventB e) { return State{LastEvent::B}; }
};

template <typename Visitor> struct Applicator {

static State apply(State s, Event e) {

/*** Start of pseudo code ***/
if (Visitor can apply) {
return Visitor::apply(s, e);
}
/*** End of pseudo code ***/

// Else, don't update state state
return s;
}
};

int main() {
// Handled by visitor
State s1 = Applicator<Visitor>::apply(State{}, EventA{});
assert(s1.last_event == Visitor::LastEvent::A);

// Handled by visitor
State s2 = Applicator<Visitor>::apply(State{}, EventB{});
assert(s2.last_event == Visitor::LastEvent::B);

// NOT handled by visitor
State s3 = Applicator<Visitor>::apply(State{}, EventC{});
assert(s3.last_event == Visitor::LastEvent::None);
}









share|improve this question















I need to fill in some template magic to make the following code snippet to work.



The problem is that I want to be able to define a visitor class for std::variant with named static methods accepting two arguments. How can I fill in Applicator::apply() to make the dispatching work?



struct EventA {};

struct EventB {};

struct EventC {};

using Event = std::variant<EventA, EventB, EventC>;

struct Visitor {
enum class LastEvent { None, A, B, C };

struct State {
LastEvent last_event = LastEvent::None;
};

static State apply(State s, EventA e) { return State{LastEvent::A}; }

static State apply(State s, EventB e) { return State{LastEvent::B}; }
};

template <typename Visitor> struct Applicator {

static State apply(State s, Event e) {

/*** Start of pseudo code ***/
if (Visitor can apply) {
return Visitor::apply(s, e);
}
/*** End of pseudo code ***/

// Else, don't update state state
return s;
}
};

int main() {
// Handled by visitor
State s1 = Applicator<Visitor>::apply(State{}, EventA{});
assert(s1.last_event == Visitor::LastEvent::A);

// Handled by visitor
State s2 = Applicator<Visitor>::apply(State{}, EventB{});
assert(s2.last_event == Visitor::LastEvent::B);

// NOT handled by visitor
State s3 = Applicator<Visitor>::apply(State{}, EventC{});
assert(s3.last_event == Visitor::LastEvent::None);
}






c++ c++17






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 55 mins ago









Deduplicator

34k64787




34k64787










asked 1 hour ago









aerkenemesis

457312




457312












  • Aside: is State really meant to be a member of Visitor?
    – Caleth
    6 secs ago


















  • Aside: is State really meant to be a member of Visitor?
    – Caleth
    6 secs ago
















Aside: is State really meant to be a member of Visitor?
– Caleth
6 secs ago




Aside: is State really meant to be a member of Visitor?
– Caleth
6 secs ago












5 Answers
5






active

oldest

votes


















4














Another solution:



using State = Visitor::State;

template<class Visitor>
struct VisitorProxy {
State s;

template<class E>
auto operator()(E const& e) -> decltype(Visitor::apply(s, e)) {
return Visitor::apply(s, e);
}

template<class E>
State operator()(E const&) const {
return s;
}
};

template <typename Visitor> struct Applicator {
static State apply(State s, Event e) {
VisitorProxy<Visitor> p{s};
return std::visit(p, e);
}
};





share|improve this answer

















  • 1




    Priorizing on const... Now that's a clever trick I didn't see before. Neat!
    – Quentin
    29 mins ago










  • @Quentin Yep, const makes it a worse conversion for VisitorProxy<Visitor>* this. Alternatively, volatile would also work.
    – Maxim Egorushkin
    21 mins ago





















4














Using the now quite common overloaded class template trick (And Maxim's trick to order the lambdas based on the constness of their operator()) to create a SFINAE-capable functor modeling the logic you're lookig for:



template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

// ...

template <typename Visitor> struct Applicator {
static typename Visitor::State apply(typename Visitor::State s, Event e) {
return std::visit(overloaded{
[&s](auto e) mutable -> decltype(Visitor::apply(s, e)) { return Visitor::apply(s, e); },
[&s](auto) { return s; }
}, e);
}
};


Note that this ICEs all versions of Clang I've tested on Wandbox, but I haven't found a workaround. Perfect forwarding is left as an exercise to the reader :)






share|improve this answer



















  • 1




    Is it not UB to pass Event into ...?
    – Maxim Egorushkin
    30 mins ago






  • 1




    @MaximEgorushkin I thought it was okay, but apparently it is implementation-defined... I'll pinch your const-based ordering instead, much safer ;)
    – Quentin
    26 mins ago



















3














If the Visitor can always apply, then the code can be as simple as



return std::visit([&](auto e) { return Visitor::apply(s, e); }, e);


But since Visitor cannot always apply, we need to use SFINAE, which requires a set of overloaded function templates. The function templates can be defined like this:



template<class EventType>
static auto applyHelper(State s, EventType e, int)
-> decltype(Visitor::apply(s, e)) // only enabled if Visitor::apply(s, e) is a valid expression
{
return Visitor::apply(s, e);
}

template<class EventType>
static State applyHelper(State s, EventType e, long) // long gives a lower precedence
// in overload resolution when argument is 0
{
return s;
}


Then the implementation of Applicator::apply can be



  static State apply(State s, Event e) {
return std::visit([&](auto e) { return applyHelper(s, e, 0); }, e);
}





share|improve this answer





























    2














    Well, std::is_invocable_r looks like the tool of choice.

    Unfortunately, you would have to get the type of the right overload, which would completely defeat the purpose.



    Instead, go one step back and use std::is_detected from library fundamentals TS v2 or equivalent and a template:



    template <class... Xs>
    using can_Visitor_apply = decltype(Visitor::apply(std::declval<Xs>()...));

    if constexpr(std::is_detected_convertible<State, can_Visitor_apply, State&, Event&>())
    return Visitor::apply(s, e);


    The advantage is that you have a compile-time-constant to hang arbitrary decisions on. The disadvantage is not (yet) having a function which you can simply just call and forget about it.






    share|improve this answer































      -1














      If I understand correctly, and you want write all without std::visit(), your Applicator::apply() can be written as follows



        static typename Visitor::State apply(typename Visitor::State s, Event e) {
      switch ( e.index() )
      {
      case 0:
      return Visitor::apply(s, std::get<0>(e));
      break;
      case 1:
      return Visitor::apply(s, std::get<1>(e));
      break;
      default:
      return s;
      }
      }


      But your code needs some correction (Visitor::State or typename Visitor::State instead of simply State).



      The following is a full compiling example



      #include <variant>

      struct EventA {};

      struct EventB {};

      struct EventC {};

      using Event = std::variant<EventA, EventB, EventC>;

      struct Visitor {
      enum class LastEvent { None, A, B, C };

      struct State {
      LastEvent last_event = LastEvent::None;
      };

      static Visitor::State apply(Visitor::State, EventA) { return Visitor::State{LastEvent::A}; }

      static Visitor::State apply(Visitor::State, EventB) { return Visitor::State{LastEvent::B}; }
      };

      template <typename Visitor> struct Applicator {

      static typename Visitor::State apply(typename Visitor::State s, Event e) {
      switch ( e.index() )
      {
      case 0:
      return Visitor::apply(s, std::get<0>(e));
      break;
      case 1:
      return Visitor::apply(s, std::get<1>(e));
      break;
      default:
      return s;
      }
      }
      };

      int main() {
      using St = typename Visitor::State;

      // Handled by visitor
      St s1 = Applicator<Visitor>::apply(St{}, EventA{});
      assert(s1.last_event == Visitor::LastEvent::A);

      // Handled by visitor
      St s2 = Applicator<Visitor>::apply(St{}, EventB{});
      assert(s2.last_event == Visitor::LastEvent::B);

      // NOT handled by visitor
      St s3 = Applicator<Visitor>::apply(St{}, EventC{});
      assert(s3.last_event == Visitor::LastEvent::None);
      }





      share|improve this answer



















      • 3




        That's doing things the hard (as in manual and error-prone) way.
        – Deduplicator
        23 mins ago










      • @Deduplicator - I'm agree; but (if I understand correctly) is exactly what the OP is trying to do.
        – max66
        22 mins ago










      • There is now way of knowing how many alternatives Event has, hence this will not work
        – aerkenemesis
        7 mins ago











      Your Answer






      StackExchange.ifUsing("editor", function () {
      StackExchange.using("externalEditor", function () {
      StackExchange.using("snippets", function () {
      StackExchange.snippets.init();
      });
      });
      }, "code-snippets");

      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "1"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: true,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: 10,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54036439%2fnamed-static-dispatching-with-stdvariant%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      5 Answers
      5






      active

      oldest

      votes








      5 Answers
      5






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      4














      Another solution:



      using State = Visitor::State;

      template<class Visitor>
      struct VisitorProxy {
      State s;

      template<class E>
      auto operator()(E const& e) -> decltype(Visitor::apply(s, e)) {
      return Visitor::apply(s, e);
      }

      template<class E>
      State operator()(E const&) const {
      return s;
      }
      };

      template <typename Visitor> struct Applicator {
      static State apply(State s, Event e) {
      VisitorProxy<Visitor> p{s};
      return std::visit(p, e);
      }
      };





      share|improve this answer

















      • 1




        Priorizing on const... Now that's a clever trick I didn't see before. Neat!
        – Quentin
        29 mins ago










      • @Quentin Yep, const makes it a worse conversion for VisitorProxy<Visitor>* this. Alternatively, volatile would also work.
        – Maxim Egorushkin
        21 mins ago


















      4














      Another solution:



      using State = Visitor::State;

      template<class Visitor>
      struct VisitorProxy {
      State s;

      template<class E>
      auto operator()(E const& e) -> decltype(Visitor::apply(s, e)) {
      return Visitor::apply(s, e);
      }

      template<class E>
      State operator()(E const&) const {
      return s;
      }
      };

      template <typename Visitor> struct Applicator {
      static State apply(State s, Event e) {
      VisitorProxy<Visitor> p{s};
      return std::visit(p, e);
      }
      };





      share|improve this answer

















      • 1




        Priorizing on const... Now that's a clever trick I didn't see before. Neat!
        – Quentin
        29 mins ago










      • @Quentin Yep, const makes it a worse conversion for VisitorProxy<Visitor>* this. Alternatively, volatile would also work.
        – Maxim Egorushkin
        21 mins ago
















      4












      4








      4






      Another solution:



      using State = Visitor::State;

      template<class Visitor>
      struct VisitorProxy {
      State s;

      template<class E>
      auto operator()(E const& e) -> decltype(Visitor::apply(s, e)) {
      return Visitor::apply(s, e);
      }

      template<class E>
      State operator()(E const&) const {
      return s;
      }
      };

      template <typename Visitor> struct Applicator {
      static State apply(State s, Event e) {
      VisitorProxy<Visitor> p{s};
      return std::visit(p, e);
      }
      };





      share|improve this answer












      Another solution:



      using State = Visitor::State;

      template<class Visitor>
      struct VisitorProxy {
      State s;

      template<class E>
      auto operator()(E const& e) -> decltype(Visitor::apply(s, e)) {
      return Visitor::apply(s, e);
      }

      template<class E>
      State operator()(E const&) const {
      return s;
      }
      };

      template <typename Visitor> struct Applicator {
      static State apply(State s, Event e) {
      VisitorProxy<Visitor> p{s};
      return std::visit(p, e);
      }
      };






      share|improve this answer












      share|improve this answer



      share|improve this answer










      answered 32 mins ago









      Maxim Egorushkin

      85.3k1199182




      85.3k1199182








      • 1




        Priorizing on const... Now that's a clever trick I didn't see before. Neat!
        – Quentin
        29 mins ago










      • @Quentin Yep, const makes it a worse conversion for VisitorProxy<Visitor>* this. Alternatively, volatile would also work.
        – Maxim Egorushkin
        21 mins ago
















      • 1




        Priorizing on const... Now that's a clever trick I didn't see before. Neat!
        – Quentin
        29 mins ago










      • @Quentin Yep, const makes it a worse conversion for VisitorProxy<Visitor>* this. Alternatively, volatile would also work.
        – Maxim Egorushkin
        21 mins ago










      1




      1




      Priorizing on const... Now that's a clever trick I didn't see before. Neat!
      – Quentin
      29 mins ago




      Priorizing on const... Now that's a clever trick I didn't see before. Neat!
      – Quentin
      29 mins ago












      @Quentin Yep, const makes it a worse conversion for VisitorProxy<Visitor>* this. Alternatively, volatile would also work.
      – Maxim Egorushkin
      21 mins ago






      @Quentin Yep, const makes it a worse conversion for VisitorProxy<Visitor>* this. Alternatively, volatile would also work.
      – Maxim Egorushkin
      21 mins ago















      4














      Using the now quite common overloaded class template trick (And Maxim's trick to order the lambdas based on the constness of their operator()) to create a SFINAE-capable functor modeling the logic you're lookig for:



      template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
      template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

      // ...

      template <typename Visitor> struct Applicator {
      static typename Visitor::State apply(typename Visitor::State s, Event e) {
      return std::visit(overloaded{
      [&s](auto e) mutable -> decltype(Visitor::apply(s, e)) { return Visitor::apply(s, e); },
      [&s](auto) { return s; }
      }, e);
      }
      };


      Note that this ICEs all versions of Clang I've tested on Wandbox, but I haven't found a workaround. Perfect forwarding is left as an exercise to the reader :)






      share|improve this answer



















      • 1




        Is it not UB to pass Event into ...?
        – Maxim Egorushkin
        30 mins ago






      • 1




        @MaximEgorushkin I thought it was okay, but apparently it is implementation-defined... I'll pinch your const-based ordering instead, much safer ;)
        – Quentin
        26 mins ago
















      4














      Using the now quite common overloaded class template trick (And Maxim's trick to order the lambdas based on the constness of their operator()) to create a SFINAE-capable functor modeling the logic you're lookig for:



      template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
      template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

      // ...

      template <typename Visitor> struct Applicator {
      static typename Visitor::State apply(typename Visitor::State s, Event e) {
      return std::visit(overloaded{
      [&s](auto e) mutable -> decltype(Visitor::apply(s, e)) { return Visitor::apply(s, e); },
      [&s](auto) { return s; }
      }, e);
      }
      };


      Note that this ICEs all versions of Clang I've tested on Wandbox, but I haven't found a workaround. Perfect forwarding is left as an exercise to the reader :)






      share|improve this answer



















      • 1




        Is it not UB to pass Event into ...?
        – Maxim Egorushkin
        30 mins ago






      • 1




        @MaximEgorushkin I thought it was okay, but apparently it is implementation-defined... I'll pinch your const-based ordering instead, much safer ;)
        – Quentin
        26 mins ago














      4












      4








      4






      Using the now quite common overloaded class template trick (And Maxim's trick to order the lambdas based on the constness of their operator()) to create a SFINAE-capable functor modeling the logic you're lookig for:



      template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
      template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

      // ...

      template <typename Visitor> struct Applicator {
      static typename Visitor::State apply(typename Visitor::State s, Event e) {
      return std::visit(overloaded{
      [&s](auto e) mutable -> decltype(Visitor::apply(s, e)) { return Visitor::apply(s, e); },
      [&s](auto) { return s; }
      }, e);
      }
      };


      Note that this ICEs all versions of Clang I've tested on Wandbox, but I haven't found a workaround. Perfect forwarding is left as an exercise to the reader :)






      share|improve this answer














      Using the now quite common overloaded class template trick (And Maxim's trick to order the lambdas based on the constness of their operator()) to create a SFINAE-capable functor modeling the logic you're lookig for:



      template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
      template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

      // ...

      template <typename Visitor> struct Applicator {
      static typename Visitor::State apply(typename Visitor::State s, Event e) {
      return std::visit(overloaded{
      [&s](auto e) mutable -> decltype(Visitor::apply(s, e)) { return Visitor::apply(s, e); },
      [&s](auto) { return s; }
      }, e);
      }
      };


      Note that this ICEs all versions of Clang I've tested on Wandbox, but I haven't found a workaround. Perfect forwarding is left as an exercise to the reader :)







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited 25 mins ago

























      answered 35 mins ago









      Quentin

      44.4k584140




      44.4k584140








      • 1




        Is it not UB to pass Event into ...?
        – Maxim Egorushkin
        30 mins ago






      • 1




        @MaximEgorushkin I thought it was okay, but apparently it is implementation-defined... I'll pinch your const-based ordering instead, much safer ;)
        – Quentin
        26 mins ago














      • 1




        Is it not UB to pass Event into ...?
        – Maxim Egorushkin
        30 mins ago






      • 1




        @MaximEgorushkin I thought it was okay, but apparently it is implementation-defined... I'll pinch your const-based ordering instead, much safer ;)
        – Quentin
        26 mins ago








      1




      1




      Is it not UB to pass Event into ...?
      – Maxim Egorushkin
      30 mins ago




      Is it not UB to pass Event into ...?
      – Maxim Egorushkin
      30 mins ago




      1




      1




      @MaximEgorushkin I thought it was okay, but apparently it is implementation-defined... I'll pinch your const-based ordering instead, much safer ;)
      – Quentin
      26 mins ago




      @MaximEgorushkin I thought it was okay, but apparently it is implementation-defined... I'll pinch your const-based ordering instead, much safer ;)
      – Quentin
      26 mins ago











      3














      If the Visitor can always apply, then the code can be as simple as



      return std::visit([&](auto e) { return Visitor::apply(s, e); }, e);


      But since Visitor cannot always apply, we need to use SFINAE, which requires a set of overloaded function templates. The function templates can be defined like this:



      template<class EventType>
      static auto applyHelper(State s, EventType e, int)
      -> decltype(Visitor::apply(s, e)) // only enabled if Visitor::apply(s, e) is a valid expression
      {
      return Visitor::apply(s, e);
      }

      template<class EventType>
      static State applyHelper(State s, EventType e, long) // long gives a lower precedence
      // in overload resolution when argument is 0
      {
      return s;
      }


      Then the implementation of Applicator::apply can be



        static State apply(State s, Event e) {
      return std::visit([&](auto e) { return applyHelper(s, e, 0); }, e);
      }





      share|improve this answer


























        3














        If the Visitor can always apply, then the code can be as simple as



        return std::visit([&](auto e) { return Visitor::apply(s, e); }, e);


        But since Visitor cannot always apply, we need to use SFINAE, which requires a set of overloaded function templates. The function templates can be defined like this:



        template<class EventType>
        static auto applyHelper(State s, EventType e, int)
        -> decltype(Visitor::apply(s, e)) // only enabled if Visitor::apply(s, e) is a valid expression
        {
        return Visitor::apply(s, e);
        }

        template<class EventType>
        static State applyHelper(State s, EventType e, long) // long gives a lower precedence
        // in overload resolution when argument is 0
        {
        return s;
        }


        Then the implementation of Applicator::apply can be



          static State apply(State s, Event e) {
        return std::visit([&](auto e) { return applyHelper(s, e, 0); }, e);
        }





        share|improve this answer
























          3












          3








          3






          If the Visitor can always apply, then the code can be as simple as



          return std::visit([&](auto e) { return Visitor::apply(s, e); }, e);


          But since Visitor cannot always apply, we need to use SFINAE, which requires a set of overloaded function templates. The function templates can be defined like this:



          template<class EventType>
          static auto applyHelper(State s, EventType e, int)
          -> decltype(Visitor::apply(s, e)) // only enabled if Visitor::apply(s, e) is a valid expression
          {
          return Visitor::apply(s, e);
          }

          template<class EventType>
          static State applyHelper(State s, EventType e, long) // long gives a lower precedence
          // in overload resolution when argument is 0
          {
          return s;
          }


          Then the implementation of Applicator::apply can be



            static State apply(State s, Event e) {
          return std::visit([&](auto e) { return applyHelper(s, e, 0); }, e);
          }





          share|improve this answer












          If the Visitor can always apply, then the code can be as simple as



          return std::visit([&](auto e) { return Visitor::apply(s, e); }, e);


          But since Visitor cannot always apply, we need to use SFINAE, which requires a set of overloaded function templates. The function templates can be defined like this:



          template<class EventType>
          static auto applyHelper(State s, EventType e, int)
          -> decltype(Visitor::apply(s, e)) // only enabled if Visitor::apply(s, e) is a valid expression
          {
          return Visitor::apply(s, e);
          }

          template<class EventType>
          static State applyHelper(State s, EventType e, long) // long gives a lower precedence
          // in overload resolution when argument is 0
          {
          return s;
          }


          Then the implementation of Applicator::apply can be



            static State apply(State s, Event e) {
          return std::visit([&](auto e) { return applyHelper(s, e, 0); }, e);
          }






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered 26 mins ago









          cpplearner

          4,92421936




          4,92421936























              2














              Well, std::is_invocable_r looks like the tool of choice.

              Unfortunately, you would have to get the type of the right overload, which would completely defeat the purpose.



              Instead, go one step back and use std::is_detected from library fundamentals TS v2 or equivalent and a template:



              template <class... Xs>
              using can_Visitor_apply = decltype(Visitor::apply(std::declval<Xs>()...));

              if constexpr(std::is_detected_convertible<State, can_Visitor_apply, State&, Event&>())
              return Visitor::apply(s, e);


              The advantage is that you have a compile-time-constant to hang arbitrary decisions on. The disadvantage is not (yet) having a function which you can simply just call and forget about it.






              share|improve this answer




























                2














                Well, std::is_invocable_r looks like the tool of choice.

                Unfortunately, you would have to get the type of the right overload, which would completely defeat the purpose.



                Instead, go one step back and use std::is_detected from library fundamentals TS v2 or equivalent and a template:



                template <class... Xs>
                using can_Visitor_apply = decltype(Visitor::apply(std::declval<Xs>()...));

                if constexpr(std::is_detected_convertible<State, can_Visitor_apply, State&, Event&>())
                return Visitor::apply(s, e);


                The advantage is that you have a compile-time-constant to hang arbitrary decisions on. The disadvantage is not (yet) having a function which you can simply just call and forget about it.






                share|improve this answer


























                  2












                  2








                  2






                  Well, std::is_invocable_r looks like the tool of choice.

                  Unfortunately, you would have to get the type of the right overload, which would completely defeat the purpose.



                  Instead, go one step back and use std::is_detected from library fundamentals TS v2 or equivalent and a template:



                  template <class... Xs>
                  using can_Visitor_apply = decltype(Visitor::apply(std::declval<Xs>()...));

                  if constexpr(std::is_detected_convertible<State, can_Visitor_apply, State&, Event&>())
                  return Visitor::apply(s, e);


                  The advantage is that you have a compile-time-constant to hang arbitrary decisions on. The disadvantage is not (yet) having a function which you can simply just call and forget about it.






                  share|improve this answer














                  Well, std::is_invocable_r looks like the tool of choice.

                  Unfortunately, you would have to get the type of the right overload, which would completely defeat the purpose.



                  Instead, go one step back and use std::is_detected from library fundamentals TS v2 or equivalent and a template:



                  template <class... Xs>
                  using can_Visitor_apply = decltype(Visitor::apply(std::declval<Xs>()...));

                  if constexpr(std::is_detected_convertible<State, can_Visitor_apply, State&, Event&>())
                  return Visitor::apply(s, e);


                  The advantage is that you have a compile-time-constant to hang arbitrary decisions on. The disadvantage is not (yet) having a function which you can simply just call and forget about it.







                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited 22 mins ago

























                  answered 28 mins ago









                  Deduplicator

                  34k64787




                  34k64787























                      -1














                      If I understand correctly, and you want write all without std::visit(), your Applicator::apply() can be written as follows



                        static typename Visitor::State apply(typename Visitor::State s, Event e) {
                      switch ( e.index() )
                      {
                      case 0:
                      return Visitor::apply(s, std::get<0>(e));
                      break;
                      case 1:
                      return Visitor::apply(s, std::get<1>(e));
                      break;
                      default:
                      return s;
                      }
                      }


                      But your code needs some correction (Visitor::State or typename Visitor::State instead of simply State).



                      The following is a full compiling example



                      #include <variant>

                      struct EventA {};

                      struct EventB {};

                      struct EventC {};

                      using Event = std::variant<EventA, EventB, EventC>;

                      struct Visitor {
                      enum class LastEvent { None, A, B, C };

                      struct State {
                      LastEvent last_event = LastEvent::None;
                      };

                      static Visitor::State apply(Visitor::State, EventA) { return Visitor::State{LastEvent::A}; }

                      static Visitor::State apply(Visitor::State, EventB) { return Visitor::State{LastEvent::B}; }
                      };

                      template <typename Visitor> struct Applicator {

                      static typename Visitor::State apply(typename Visitor::State s, Event e) {
                      switch ( e.index() )
                      {
                      case 0:
                      return Visitor::apply(s, std::get<0>(e));
                      break;
                      case 1:
                      return Visitor::apply(s, std::get<1>(e));
                      break;
                      default:
                      return s;
                      }
                      }
                      };

                      int main() {
                      using St = typename Visitor::State;

                      // Handled by visitor
                      St s1 = Applicator<Visitor>::apply(St{}, EventA{});
                      assert(s1.last_event == Visitor::LastEvent::A);

                      // Handled by visitor
                      St s2 = Applicator<Visitor>::apply(St{}, EventB{});
                      assert(s2.last_event == Visitor::LastEvent::B);

                      // NOT handled by visitor
                      St s3 = Applicator<Visitor>::apply(St{}, EventC{});
                      assert(s3.last_event == Visitor::LastEvent::None);
                      }





                      share|improve this answer



















                      • 3




                        That's doing things the hard (as in manual and error-prone) way.
                        – Deduplicator
                        23 mins ago










                      • @Deduplicator - I'm agree; but (if I understand correctly) is exactly what the OP is trying to do.
                        – max66
                        22 mins ago










                      • There is now way of knowing how many alternatives Event has, hence this will not work
                        – aerkenemesis
                        7 mins ago
















                      -1














                      If I understand correctly, and you want write all without std::visit(), your Applicator::apply() can be written as follows



                        static typename Visitor::State apply(typename Visitor::State s, Event e) {
                      switch ( e.index() )
                      {
                      case 0:
                      return Visitor::apply(s, std::get<0>(e));
                      break;
                      case 1:
                      return Visitor::apply(s, std::get<1>(e));
                      break;
                      default:
                      return s;
                      }
                      }


                      But your code needs some correction (Visitor::State or typename Visitor::State instead of simply State).



                      The following is a full compiling example



                      #include <variant>

                      struct EventA {};

                      struct EventB {};

                      struct EventC {};

                      using Event = std::variant<EventA, EventB, EventC>;

                      struct Visitor {
                      enum class LastEvent { None, A, B, C };

                      struct State {
                      LastEvent last_event = LastEvent::None;
                      };

                      static Visitor::State apply(Visitor::State, EventA) { return Visitor::State{LastEvent::A}; }

                      static Visitor::State apply(Visitor::State, EventB) { return Visitor::State{LastEvent::B}; }
                      };

                      template <typename Visitor> struct Applicator {

                      static typename Visitor::State apply(typename Visitor::State s, Event e) {
                      switch ( e.index() )
                      {
                      case 0:
                      return Visitor::apply(s, std::get<0>(e));
                      break;
                      case 1:
                      return Visitor::apply(s, std::get<1>(e));
                      break;
                      default:
                      return s;
                      }
                      }
                      };

                      int main() {
                      using St = typename Visitor::State;

                      // Handled by visitor
                      St s1 = Applicator<Visitor>::apply(St{}, EventA{});
                      assert(s1.last_event == Visitor::LastEvent::A);

                      // Handled by visitor
                      St s2 = Applicator<Visitor>::apply(St{}, EventB{});
                      assert(s2.last_event == Visitor::LastEvent::B);

                      // NOT handled by visitor
                      St s3 = Applicator<Visitor>::apply(St{}, EventC{});
                      assert(s3.last_event == Visitor::LastEvent::None);
                      }





                      share|improve this answer



















                      • 3




                        That's doing things the hard (as in manual and error-prone) way.
                        – Deduplicator
                        23 mins ago










                      • @Deduplicator - I'm agree; but (if I understand correctly) is exactly what the OP is trying to do.
                        – max66
                        22 mins ago










                      • There is now way of knowing how many alternatives Event has, hence this will not work
                        – aerkenemesis
                        7 mins ago














                      -1












                      -1








                      -1






                      If I understand correctly, and you want write all without std::visit(), your Applicator::apply() can be written as follows



                        static typename Visitor::State apply(typename Visitor::State s, Event e) {
                      switch ( e.index() )
                      {
                      case 0:
                      return Visitor::apply(s, std::get<0>(e));
                      break;
                      case 1:
                      return Visitor::apply(s, std::get<1>(e));
                      break;
                      default:
                      return s;
                      }
                      }


                      But your code needs some correction (Visitor::State or typename Visitor::State instead of simply State).



                      The following is a full compiling example



                      #include <variant>

                      struct EventA {};

                      struct EventB {};

                      struct EventC {};

                      using Event = std::variant<EventA, EventB, EventC>;

                      struct Visitor {
                      enum class LastEvent { None, A, B, C };

                      struct State {
                      LastEvent last_event = LastEvent::None;
                      };

                      static Visitor::State apply(Visitor::State, EventA) { return Visitor::State{LastEvent::A}; }

                      static Visitor::State apply(Visitor::State, EventB) { return Visitor::State{LastEvent::B}; }
                      };

                      template <typename Visitor> struct Applicator {

                      static typename Visitor::State apply(typename Visitor::State s, Event e) {
                      switch ( e.index() )
                      {
                      case 0:
                      return Visitor::apply(s, std::get<0>(e));
                      break;
                      case 1:
                      return Visitor::apply(s, std::get<1>(e));
                      break;
                      default:
                      return s;
                      }
                      }
                      };

                      int main() {
                      using St = typename Visitor::State;

                      // Handled by visitor
                      St s1 = Applicator<Visitor>::apply(St{}, EventA{});
                      assert(s1.last_event == Visitor::LastEvent::A);

                      // Handled by visitor
                      St s2 = Applicator<Visitor>::apply(St{}, EventB{});
                      assert(s2.last_event == Visitor::LastEvent::B);

                      // NOT handled by visitor
                      St s3 = Applicator<Visitor>::apply(St{}, EventC{});
                      assert(s3.last_event == Visitor::LastEvent::None);
                      }





                      share|improve this answer














                      If I understand correctly, and you want write all without std::visit(), your Applicator::apply() can be written as follows



                        static typename Visitor::State apply(typename Visitor::State s, Event e) {
                      switch ( e.index() )
                      {
                      case 0:
                      return Visitor::apply(s, std::get<0>(e));
                      break;
                      case 1:
                      return Visitor::apply(s, std::get<1>(e));
                      break;
                      default:
                      return s;
                      }
                      }


                      But your code needs some correction (Visitor::State or typename Visitor::State instead of simply State).



                      The following is a full compiling example



                      #include <variant>

                      struct EventA {};

                      struct EventB {};

                      struct EventC {};

                      using Event = std::variant<EventA, EventB, EventC>;

                      struct Visitor {
                      enum class LastEvent { None, A, B, C };

                      struct State {
                      LastEvent last_event = LastEvent::None;
                      };

                      static Visitor::State apply(Visitor::State, EventA) { return Visitor::State{LastEvent::A}; }

                      static Visitor::State apply(Visitor::State, EventB) { return Visitor::State{LastEvent::B}; }
                      };

                      template <typename Visitor> struct Applicator {

                      static typename Visitor::State apply(typename Visitor::State s, Event e) {
                      switch ( e.index() )
                      {
                      case 0:
                      return Visitor::apply(s, std::get<0>(e));
                      break;
                      case 1:
                      return Visitor::apply(s, std::get<1>(e));
                      break;
                      default:
                      return s;
                      }
                      }
                      };

                      int main() {
                      using St = typename Visitor::State;

                      // Handled by visitor
                      St s1 = Applicator<Visitor>::apply(St{}, EventA{});
                      assert(s1.last_event == Visitor::LastEvent::A);

                      // Handled by visitor
                      St s2 = Applicator<Visitor>::apply(St{}, EventB{});
                      assert(s2.last_event == Visitor::LastEvent::B);

                      // NOT handled by visitor
                      St s3 = Applicator<Visitor>::apply(St{}, EventC{});
                      assert(s3.last_event == Visitor::LastEvent::None);
                      }






                      share|improve this answer














                      share|improve this answer



                      share|improve this answer








                      edited 23 mins ago

























                      answered 25 mins ago









                      max66

                      34.5k63762




                      34.5k63762








                      • 3




                        That's doing things the hard (as in manual and error-prone) way.
                        – Deduplicator
                        23 mins ago










                      • @Deduplicator - I'm agree; but (if I understand correctly) is exactly what the OP is trying to do.
                        – max66
                        22 mins ago










                      • There is now way of knowing how many alternatives Event has, hence this will not work
                        – aerkenemesis
                        7 mins ago














                      • 3




                        That's doing things the hard (as in manual and error-prone) way.
                        – Deduplicator
                        23 mins ago










                      • @Deduplicator - I'm agree; but (if I understand correctly) is exactly what the OP is trying to do.
                        – max66
                        22 mins ago










                      • There is now way of knowing how many alternatives Event has, hence this will not work
                        – aerkenemesis
                        7 mins ago








                      3




                      3




                      That's doing things the hard (as in manual and error-prone) way.
                      – Deduplicator
                      23 mins ago




                      That's doing things the hard (as in manual and error-prone) way.
                      – Deduplicator
                      23 mins ago












                      @Deduplicator - I'm agree; but (if I understand correctly) is exactly what the OP is trying to do.
                      – max66
                      22 mins ago




                      @Deduplicator - I'm agree; but (if I understand correctly) is exactly what the OP is trying to do.
                      – max66
                      22 mins ago












                      There is now way of knowing how many alternatives Event has, hence this will not work
                      – aerkenemesis
                      7 mins ago




                      There is now way of knowing how many alternatives Event has, hence this will not work
                      – aerkenemesis
                      7 mins ago


















                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to Stack Overflow!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      To learn more, see our tips on writing great answers.





                      Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                      Please pay close attention to the following guidance:


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54036439%2fnamed-static-dispatching-with-stdvariant%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      Eastern Orthodox Church

                      Zagreb

                      Understanding the information contained in the Deep Space Network XML data?