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

Поділитися
Вставка
  • Опубліковано 20 гру 2024

КОМЕНТАРІ •

  • @Minty_Meeo
    @Minty_Meeo 4 місяці тому +46

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

    • @yato3335
      @yato3335 4 місяці тому +3

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

    • @minirop
      @minirop 4 місяці тому +11

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

    • @wowyomad
      @wowyomad 4 місяці тому

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

  • @atheist800
    @atheist800 4 місяці тому +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.

  • @Marlonbr1
    @Marlonbr1 4 місяці тому +6

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

  • @jeremypewterschmidt664
    @jeremypewterschmidt664 3 місяці тому +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 4 місяці тому +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 4 місяці тому

      Could somebody please explain?❤

    • @irrationalnumbers771
      @irrationalnumbers771 4 місяці тому

      @@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 4 місяці тому

      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 4 місяці тому

      @@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 2 місяці тому

      you just return another variant )

  • @kaosce
    @kaosce 4 місяці тому +3

    Why is there no standard visitor class?

  • @ImmortalGuard23
    @ImmortalGuard23 4 місяці тому

    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)

  • @josephlunderville3195
    @josephlunderville3195 4 місяці тому +3

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

    • @Henrik0x7F
      @Henrik0x7F 4 місяці тому +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?

  • @aniketbisht2823
    @aniketbisht2823 4 місяці тому +1

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

  • @HaraldAchitz
    @HaraldAchitz 4 місяці тому +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 4 місяці тому

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

    • @MrAlbinopapa
      @MrAlbinopapa 4 місяці тому +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.

  • @vetirtal1168
    @vetirtal1168 4 місяці тому +1

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

    • @Traian-Enache
      @Traian-Enache 4 місяці тому +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 4 місяці тому

      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.

  • @Megalcristo2
    @Megalcristo2 4 місяці тому +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

  • @yato3335
    @yato3335 4 місяці тому +2

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

    • @matt42hughes
      @matt42hughes 4 місяці тому

      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 4 місяці тому

      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 4 місяці тому

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

    • @ToMikaa87
      @ToMikaa87 4 місяці тому

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

  • @MrAlbinopapa
    @MrAlbinopapa 4 місяці тому

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

    • @coder2k
      @coder2k 4 місяці тому +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!

  • @NotherPleb
    @NotherPleb 4 місяці тому

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

    • @cppweekly
      @cppweekly  4 місяці тому +1

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

  • @ohwow2074
    @ohwow2074 4 місяці тому +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.

  • @anon_y_mousse
    @anon_y_mousse 4 місяці тому

    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  4 місяці тому

      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 4 місяці тому

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

  • @sadunozer2241
    @sadunozer2241 Місяць тому

  • @TsvetanDimitrov1976
    @TsvetanDimitrov1976 4 місяці тому +1

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

  • @enzosimone86
    @enzosimone86 4 місяці тому

    ua-cam.com/video/et1fjd8X1ho/v-deo.html what can be also static starting from C++23?

    • @Raspredval1337
      @Raspredval1337 4 місяці тому +2

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

  • @KX36
    @KX36 4 місяці тому +3

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

  • @DamianReloaded
    @DamianReloaded 4 місяці тому

    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

  • @acf2802
    @acf2802 4 місяці тому +3

    Why is std::print a pile of crap?

    • @Raspredval1337
      @Raspredval1337 4 місяці тому +1

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

  • @norton7715
    @norton7715 4 місяці тому +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 4 місяці тому

      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 4 місяці тому

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

    • @tbkih
      @tbkih 4 місяці тому

      @@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 4 місяці тому

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

  • @victotronics
    @victotronics 4 місяці тому

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

    • @corpseopera
      @corpseopera 2 місяці тому

      Why would he know about that

  • @NosebergEatzbugsVonShekelstein
    @NosebergEatzbugsVonShekelstein 4 місяці тому

    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.

  • @Danfranschwan2
    @Danfranschwan2 4 місяці тому +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 4 місяці тому +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 4 місяці тому +1

      Skill issue

  • @coc1841
    @coc1841 4 місяці тому +2

    C++ has become an abominable language

    • @wizardy6267
      @wizardy6267 4 місяці тому

      Became, like over 30yrs ago from Oct 1991

    • @WilhelmDrake
      @WilhelmDrake 4 місяці тому +1

      Then why are you watching the video?

    • @coc1841
      @coc1841 4 місяці тому +1

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