C++ Weekly - Ep 440 - Revisiting Visitors for std::visit

Поделиться
HTML-код
  • Опубликовано: 19 ноя 2024

Комментарии • 62

  • @Minty_Meeo
    @Minty_Meeo 3 месяца назад +45

    The fact that you can multiple-inherit from lambda types... C++ is so wild.

    • @yato3335
      @yato3335 3 месяца назад +3

      Ikr, every time I watch these videos, I learn something new about C++

    • @minirop
      @minirop 3 месяца назад +11

      since lambda desugar to a struct with operator() it's less "magical" (the syntax still looks like it thought).

    • @wowyomad
      @wowyomad 3 месяца назад

      ​@@miniropso it's like functional interface in java

  • @Marlonbr1
    @Marlonbr1 3 месяца назад +6

    Thanks again Jason for explaining complex concepts in a very simple, understandable way !

  • @atheist800
    @atheist800 3 месяца назад +8

    Be mindful of the `const` qualifier when taking arguments by reference. I've had some surprises when I forgot to add `const` and overload resolution picked the default `auto &` case. This is especially subtle if this is a sub-element of a larger object. The `const` further up the ownership chain can be implicitly propagated down. It's easy to overlook this.

  • @jeremypewterschmidt664
    @jeremypewterschmidt664 2 месяца назад +1

    This is pretty useful in a tight loop which you may wanna do some configurations before the loop starts with some boolean variables which will be evaluated in the loop. At this time, the runtime checking will create a branch which may break something like pipeline. But with `std::visit` and `std::variant` you van evaluate it in compile time.

  • @gregorssamsa
    @gregorssamsa 3 месяца назад +4

    Note that std::visit requires return types to match for all covered types. This becomes a pain because you must leak abstractions down.

    • @Rojfos
      @Rojfos 3 месяца назад

      Could somebody please explain?❤

    • @irrationalnumbers771
      @irrationalnumbers771 3 месяца назад

      @@Rojfos The example in the video is trivial in that the return type (void) is independent of the input type. If however you have a visitor that has a return type that depends on the input type, then it gets more complicated as @gregossamsa eluded to

    • @goncalogomes5036
      @goncalogomes5036 3 месяца назад

      in that case can't you just return a variant (I guess you need to be explicit about that in the return of the lambdas)

    • @irrationalnumbers771
      @irrationalnumbers771 3 месяца назад

      @@goncalogomes5036 it all depends on your use case. For mine, the visitor would have some constexpr statements to determine how to handle the different variant types depending on the stored value type and modify an array. No return type was needed. The array would either be an array of variants or some templated container that didn't care what the underlying type was.
      I agree in principle though that returning a variant would also be possible from a lambda.

    • @vgglv
      @vgglv Месяц назад

      you just return another variant )

  • @ImmortalGuard23
    @ImmortalGuard23 3 месяца назад

    Very nice video! Thanks a lot! I would love to see part two of this where you go into details of implicit conversion pitfalls of the overload pattern and how to deal with these! (Andreas Fertig has a great blog post on safely visiting a std::variant)

  • @kaosce
    @kaosce 3 месяца назад +3

    Why is there no standard visitor class?

  • @HaraldAchitz
    @HaraldAchitz 3 месяца назад +6

    Wonderful example of why we need pattern matching, all the workarounds we have to do because we do not have it are just painful

    • @Evan490BC
      @Evan490BC 3 месяца назад

      My thought exactly! Bring the bloody pattern matching into C++ already!

    • @MrAlbinopapa
      @MrAlbinopapa 3 месяца назад +1

      @@Evan490BC Right? I'd be okay with something like:
      switch( my_variant ){
      case: // do something with circle
      case: // do something with rectangle
      }
      I did try creating an interface for something like this.
      template struct Switch{
      };
      template struct Case{
      };
      Can't remember how I had it set up, it's been a few years. Basically, the program would have to compare each template Case parameter. Once a match is found, it executes the given Fn within the Case struct. It does clean up some of the inline code because it can be defined somewhere else, but has some added runtime cost.

  • @ohwow2074
    @ohwow2074 3 месяца назад +3

    Damn this is crazy! I've never seen this sort of magic. Thanks for letting us know about this. Though I'm not a big fan of visitor.

  • @aniketbisht2823
    @aniketbisht2823 3 месяца назад +1

    0:46 Jason, no doubt is a constexpr god.

  • @josephlunderville3195
    @josephlunderville3195 3 месяца назад +3

    Pattern matching, but obtuse and not directly supported by the compiler

    • @Henrik0x7F
      @Henrik0x7F 3 месяца назад +4

      Granted you are right. Though it still shows how powerful C++ is. In what other language would you be able to implement something like this in compile time and without language level support?

  • @sadunozer2241
    @sadunozer2241 25 дней назад

  • @yato3335
    @yato3335 3 месяца назад +2

    This is crazy, I thought the current way of doing it was a chain of
    if constexpr (std::same_as)

    • @matt42hughes
      @matt42hughes 3 месяца назад

      these trivial examples are ok with only one line in each lambda, but i think i’d prefer reading the ‘if constexpr (std::same_as_v)’ version in a single large lambda. imo having multiple lambdas as args seems like it could be a little harder to follow/parse. I guess it’s down to personal preference.

    • @MrAlbinopapa
      @MrAlbinopapa 3 месяца назад

      std::same_as is a concept, I think you mean std::is_same_v.
      I've played around with std::variant and have come up with several ways of dealing with variants.
      1) Like inheritance.
      struct Circle{
      float area()const;
      };
      struct Rectangle{
      float area()const;
      };
      struct Shape{
      float area()const{
      return std::visit( []( auto const& shape_ ){
      return shape_.area();
      }, m_shape_var );
      }
      std::variant m_shape_var;
      };
      Usage: const auto area = shape.area();
      2) Conditional std::get_if or std::holds_alternative
      if( Circle* ptr = std::get_if( &shape.m_shape_var );ptr ) { // do things }
      else if( Rectangle* ptr = std::get_if( &shape.m_shape_var ){ do other things }
      if( std::holds_alternative( shape.m_shape_var ) ){ // do things }
      else if( std::holds_alternative( shape.m_shape_var) ){ // do other things }
      3) Static dispatch; using both methods listed in this video
      4) if constexpr block
      std::visit( [&]( auto const& var_ ){
      using type = std::decay_t;
      if constexpr( std::is_same_v ){
      // I do this so intellisense ( Visual Studio ) can show what's available
      Circle const& circle = var_;
      }
      else if constexpr( std::is_same_v ) {
      Rectangle const& rectangle = var_;
      }
      }, shape.m_shape_var );
      My favorite use for std::variant is definitely in place of inheritance. This means I can create a single interface ( Shape ) and hide the implementation details in the pseudo derived classes. This also reduces the need for any of the other methods for dealing with specific types.

    • @yato3335
      @yato3335 3 месяца назад

      @@MrAlbinopapa I clearly remember that concepts are "convertible" to booleans. And you can apply boolean && and || operators on them

    • @ToMikaa87
      @ToMikaa87 3 месяца назад

      @@MrAlbinopapa In case of 4), you can also simplify it using a lambda with an explicit template parameter list:
      std::visit( [&]( T const& var_ ){
      if constexpr( std::is_same_v ){
      // I do this so intellisense ( Visual Studio ) can show what's available
      Circle const& circle = var_;
      }
      else if constexpr( std::is_same_v ) {
      Rectangle const& rectangle = var_;
      }
      }, shape.m_shape_var );

  • @Megalcristo2
    @Megalcristo2 3 месяца назад +1

    I dont understand why this isnt the default, I can see that maybe you want to use an instance of a functor but most probably Inwould die before I would need that, instead this looks like the 99,99% use case for a visitor, but somehow we have to do this ourselves instead of a library doing it for us

  • @vetirtal1168
    @vetirtal1168 3 месяца назад +1

    But why would I want to use this over std::holds_alternative in an if/else-if/else statement?

    • @Traian-Enache
      @Traian-Enache 3 месяца назад +5

      Because an if-else-if chain is an if-else-if chain, potentially evaluating the condition for all branches (and thus may branch N-1 times given N branches), which are not cheap.
      std::visit *may* be implemented using a table of function pointers, thus requiring only an array access into said table, and calling the obtained function pointer (which in turn casts the variant storage to a reference of the contained type and calling the apropriate overload of the provided function object).
      This results in a single “indirect jump/call”, which is faster than an arbitrary amount of branches.

    • @FruchteisMitErdbeer
      @FruchteisMitErdbeer 3 месяца назад

      Mainly because this does exhaustiveness checking. All variant types must be covered by one of the lambdas (or call operators if you're hand-writing a visitor), or you get a compile-time error.
      That way, when you add a type to the variant later, the compiler will helpfully point out all the places you need to adapt in your code.

  • @MrAlbinopapa
    @MrAlbinopapa 3 месяца назад

    Didn't we also get std::overload to do the exact same thing as the 'visitor' template struct you made?

    • @coder2k
      @coder2k 3 месяца назад +1

      As far as I know, there is no such thing as std::overload. But on cppreference there's a code example with a struct called "overloaded" that's pretty similar to what Jason did in this video. You can find it on the page about std::visit. It would be cool if that would be standardised, though!

  • @TsvetanDimitrov1976
    @TsvetanDimitrov1976 3 месяца назад +1

    very neat implementation. i still want to have a generalized pattern matching if possible in the standard though.

  • @anon_y_mousse
    @anon_y_mousse 3 месяца назад

    This one feels like more of a hacky kludge from the committee to address a problem that would've been better addressed by adding on to switches. I'm sure if someone reads this they'll claim that changing switches would potentially break existing code somewhere somehow, but if the compiler is written correctly then it's simply a matter of context switching based on what types are being cased on and it shouldn't break anything if the compilers are written the correct way.

    • @cppweekly
      @cppweekly  3 месяца назад

      I wouldn't look at this as a kludge from the committee - it's a thing that users came up with.

    • @anon_y_mousse
      @anon_y_mousse 3 месяца назад

      @@cppweekly It's in the standard though, which means they approved it.

  • @NotherPleb
    @NotherPleb 3 месяца назад

    This is not working for me on c++20 clang, anyone else got any idea?

    • @cppweekly
      @cppweekly  3 месяца назад +1

      Depends on the version of clang, but it was slow implementing this feature.

  • @KX36
    @KX36 3 месяца назад +3

    I don't understand why you always shoehorn the Clion adverts in the middle of a sentence. Nobody else does that.

  • @DamianReloaded
    @DamianReloaded 3 месяца назад

    int main() {
    auto print = [](auto y){std::visit([](auto x){
    struct print {
    print (int i) { printf("%i
    ",i);}
    print (float f) { printf("%f
    ",f);}
    print (std::string_view sv) { printf("%.*s
    ", static_cast(sv.size()), sv.data()); }
    } print(x);
    }, y);};
    std::variant var = 123;
    print(var);
    var = "Hello World!";
    print(var);
    }
    aspirationally:
    int main() {
    std::visit( [](auto val){ std::cerr

  • @enzosimone86
    @enzosimone86 3 месяца назад

    ruclips.net/video/et1fjd8X1ho/видео.html what can be also static starting from C++23?

    • @Raspredval1337
      @Raspredval1337 3 месяца назад +2

      the call operator ('operator( )') can now be static

  • @acf2802
    @acf2802 3 месяца назад +3

    Why is std::print a pile of crap?

    • @Raspredval1337
      @Raspredval1337 3 месяца назад +1

      it's a new feature, it's gonna be good eventually

  • @norton7715
    @norton7715 3 месяца назад +3

    void print_value(int i) {
    fmt::print("Int: {}
    ", i);
    }
    void print_value(float f) {
    fmt::print("Float: {}
    ", f);
    }
    void print_value(std::string_view sv) {
    fmt::print("SV: {}
    ", sv);
    }
    void print(value_t value) {
    std::visit([](auto value){ print_value(value); }, value);
    }
    int main() {
    const auto value = get_variant();
    print(value);
    }

    • @vytah
      @vytah 3 месяца назад

      You're moving the code for each case into separate functions, breaking the code flow. I no longer see what the print method does at a single glance, because you forced me to scour the source for all the implementations of print_value.

    • @norton7715
      @norton7715 3 месяца назад

      @@vytah yes if you only write slideware then jason's version is better

    • @tbkih
      @tbkih 3 месяца назад

      @@norton7715 Even if you don't I would argue it's still nicer to use the clever fancy "visitor" multi-operator CTAD object with the lambdas, and _then_ delegate to print_int, print_float and print_string. Precisely for the reasons @vytah invokes: you get to see the dispatching table in one place.

    • @norton7715
      @norton7715 3 месяца назад

      @@tbkih the code i posted does exactly what you are describing

  • @victotronics
    @victotronics 3 месяца назад

    "Breda" has the stress on the 2nd syllable. "The Netherlands" has the definite article as part of the name.

    • @corpseopera
      @corpseopera Месяц назад

      Why would he know about that

  • @Danfranschwan2
    @Danfranschwan2 3 месяца назад +2

    can this code style be anymore obscure and hard to read? ... drifting towards gimmicks don't you think? gimme real life problems that are better solved using this.

    • @enzosimone86
      @enzosimone86 3 месяца назад +1

      You can have a method returning a std::variant, so the visitor MUST handle all possible returned types, some sort of pattern matching.

    • @gregorssamsa
      @gregorssamsa 3 месяца назад +1

      Skill issue

  • @NosebergEatzbugsVonShekelstein
    @NosebergEatzbugsVonShekelstein 3 месяца назад

    Screw all this noise, just check holds_alternative and act accordingly. Real life uses of std::variant aren't for integers and floats. A common use I see is for UI elements like buttons and windows and menus, where you have a sender and a receiver and the receiver needs to act according to it's sender. A generic lambda setup like this would just obfuscate and clutter the code.

  • @coc1841
    @coc1841 3 месяца назад +2

    C++ has become an abominable language

    • @wizardy6267
      @wizardy6267 3 месяца назад

      Became, like over 30yrs ago from Oct 1991

    • @WilhelmDrake
      @WilhelmDrake 3 месяца назад +1

      Then why are you watching the video?

    • @coc1841
      @coc1841 3 месяца назад +1

      @@WilhelmDrake Coz' I'm a C++ dev, but I just don't like the language as much as before.