C++ Weekly - Ep 454 - std::apply vs std::invoke (and how they work!)

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

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

  • @KyleThompson228
    @KyleThompson228 Месяц назад +38

    I'm casting my vote in favor of more template metaprogramming on this channel! Very cool stuff.

    • @jamesdowner
      @jamesdowner Месяц назад +3

      Ditto

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

      Yes, very cool indeed. What could be cooler than writing programs without means of debugging or even rudimentary logging, and with very obscure error messages! Neat! [this is heavy sarcasm, for those who are slow in the head]
      And then people genuinely wonder why the govt declares C++ as unsafe...

  • @eggmeister6641
    @eggmeister6641 Месяц назад +22

    I actually find this type of programming very fascinating. Would love to see more of it on this channel!

  • @lexer_
    @lexer_ Месяц назад +4

    With this bite-sized format having more difficult and dense content is much easier to digest and its much less painful to just watch it again if necessary. I like it.

  • @BigPapaMitchell
    @BigPapaMitchell Месяц назад +11

    Here's one reason why you might have your arguments in tuples, and it's something that I had worked with recently: If you want to use templates to deduce function arguments and store them in a generic way, the easiest way to do this is to store them in a tuple deduced from the template pack, and then you can use that tuple to apply to a function later. Invoke is much easier to use when you need to locally call a function where it's being passed in.
    I used this when trying to write a little worker thread tool, where it would store arguments in a pack, then invoke the function later when you wanted to start the worker thread. Don't think there would have bee a good way to write this with invoke if I needed to defer the function calling to a different point in time since I wanted to be able to do things like store the arguments and allow the worker thread to be called multiple times with the same arguments

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

      Maybe I did not understand the second part of your comment, but from what I understand I think storing function args should be possible using std::bind or lambda expression.

    • @garyp.7501
      @garyp.7501 Месяц назад

      boost lambda library does this. (stores the arguments and nested fn's in tuples)

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

    I haven't appreciated the amount of work put into the std type_traits until I decided to implement as much of it as I could myself in c++11. The monstrous implementation of the integer_sequence (the thing index_sequence is based on) spanned over a couple of pages of the most arcane looking code I've written in my life. Mad respect for the stl writers

    • @AlfredoCorrea
      @AlfredoCorrea Месяц назад +1

      I am curious why you would want to implement this part of the library your self. IMO it is almost part of the language; not only that, AFAIK some of these functions use “compiler magic” (that the STL is allowed to use)
      PS: reading your message carefully I understand that you wanted to backport things to C++11. If so, I feel your pain, I had to reimplement tuple to make it work in the GPU.

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

      @@AlfredoCorrea there's not that many compiler intrinsics as one might think. Almost 90% of type_traits is implemented in c++11.
      As to why would anyone want to reimplement type_traits: it's a fun challenge, plus it gives insights into the inner workings of c++ metaprogramming

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

      @@AlfredoCorrea idk, making a basic recursive tuple was the easiest part, imo

    • @ensuretime
      @ensuretime 28 дней назад +1

      @@AlfredoCorrea I also implemented my “own stl” from scratch, it was fun and it's a much more basic and lightweight version without things I don't use... another thing I wanted was a readable version because let's face it, the msvc STL (the stl I use in some projects) is a real mess...
      i.e: I don't have a “span” in my stl, instead I have “view” and I specialize some types like “string_view” which derives from “view”, basically I have more basic containers that allow me to write a top-level container with less work... my vector is customized and I can choose how it should grow, if it reserves any initial memory, etc, much more control...
      Currently i've been using my stl on a high performance server using iocp on windows and i haven't had any problems or bugs related to the library itself, the tests i do are enough... The most interesting thing is that I have a namespace “::mt” which derives the other containers and implements multithread support, this design worked well for me... I still use some std:: features that I haven't implemented (mutex/atomic/format), but not the rest...

    • @AlfredoCorrea
      @AlfredoCorrea 28 дней назад

      @ interesting. I hate span as well, I implemented multi dimensional arrays library with the 1d special case that has the right semantics that span should have (search for it). Regarding implementing your own STL, I wonder where else you drew the line, did you implement sort or stable_sort for example?

  • @theicebeardk
    @theicebeardk Месяц назад +3

    This was very interesting and gave me a few ideas. I would suggest adding a concept for callable in there and be done with it :)

  • @TheClonerx
    @TheClonerx Месяц назад +7

    std::invoke & std::apply do take by reference, it's only the algorithms that take callables by value.

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

      Also they are newer than the algorithms which cannot be upgraded to use references for backward compatible I suppose.

  • @skaloczdebosz
    @skaloczdebosz 21 день назад

    Yup. I just found out how to do in 6 lines what I did in 40 lines. And in a much simpler way, too. Thx.

  • @OskarSigvardsson
    @OskarSigvardsson Месяц назад +3

    I've actually wondered this for a while: why does the standard library always pass templated function params by value? Why not by universal reference? Seems like it could introduce unnecessary copies of the functor when you just want to call the thing.

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

      This is historical, there was not forwarding references originally. the best next thing was to pass by copy and that also fitted Stepanov philosophy that functors should always be small and if you really needed to avoid copies of functor values you would use std::reference_wrapper and std::ref (which precisely overloads operator() for functors!)

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

      @ But so there’s no reason to do it in modern code then? If I’m writing a thing that takes a callback and I don’t need ownership, I shouldn’t pass by value, I should pass using universal reference?

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

      @@OskarSigvardsson legacy reasons. The same way QT is stuck with COW strings and a non-moving implementation of a vector even tho c++11 vector design (with move references) is much better and, some might say, stl strings with small string optimization are also better for general cases

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

      @@OskarSigvardsson Good question, I think it really depends on the algorithm. Passing by universal reference is very powerful but then you have to forward it somewhere internally to take advantage of it. most functor in algorithms are evaluated multiple times and therefore it is not ok to forward during the call operation. using Universal refernces take arguments is not wrong but it is borderline overengineering IMO in this context. It is preferable to put the energy into making the functors small or efficiently movable.
      If by callback you mean something that is evaluated once and that evaluation might (generically) take advantage of stealing the guts of the functor then yes.

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

    Where would you put constraints for metaprogramming vs. constexpr? I donʼt *think* theyʼre actually Turing complete but you can do many things you would actually want to do with template metaprogramming without an explicit `template` keyword

  • @ric.larsson
    @ric.larsson Месяц назад +3

    Imagine reflection, when it is easy to define what std::get on a custom struct does, and a data-type that just wraps the data needed can be manually created so that an apply may work -_- it will make code so short and not understandable

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

      you can already turn trivial structs into tuples using c++17 structured bindings, so yeah, that's a thing

  • @JunZhang-ralphjzhang
    @JunZhang-ralphjzhang Месяц назад

    i struggle loooooooooong time on std::index_sequence, this is probably the first time I get an idea "why" it is there

  • @iamjadedhobo
    @iamjadedhobo Месяц назад +5

    It is better to encounter this with a concise explanation then having to decode this cold in some bit of code without any hints.

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

    I wonder why there isn't an overloaded version of invoke that takes a touple

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

      You need to consider functions that take a tuple as argument.
      void fun(std::tuple);
      std::invoke(&fun, std::tuple{1, 2});
      With your suggested overloaded function this would instead try to call fun(1, 2).

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

    I don't see how how your apply implementation applies to std::variant. It should not expand the pack but just call the right overload

  • @zamf
    @zamf Месяц назад +4

    Let's hope that C++ reflections replace most of the variadic expansion code, so we get rid of the cryptic ellipses.

    • @redcrafterlppa303
      @redcrafterlppa303 Месяц назад +1

      As someone coming from java looking from the perspective of someone who wants more compile time facts reflection is a pain in the but and runtime typing is a guessing game you always loose.

    • @sqlexp
      @sqlexp Месяц назад +5

      ​@@redcrafterlppa303C++ reflection is executed at compile time; it is part of metaprogramming as C++ does not produce detailed compiled metadata for reflection other than RTTI. Though it allows things that current template programming cannot do, it won't make code easier to read/maintain than templates for things that templates can do.

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

      @@redcrafterlppa303 Runtime typing is considered bad coding practice in C++ and if you're forced to do it this just reveals a deep problem with your design. As for reflection in C++ it is entirely done at compile-time; it has nothing to do with runtime type guessing. Reflection just lets you write procedural-style code in places where only declarative-style code is currently accepted.

  • @combatcorgiofficial
    @combatcorgiofficial Месяц назад +7

    That’s certainly… something…

  • @anon_y_mousse
    @anon_y_mousse Месяц назад +5

    Personally, I think a lot of the syntax decisions that have been made for C++ and other "modern" languages have been awful. However, these videos are quite instructive of what direction not to head with my own language, so keep 'em coming, I say.

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

      Most currently modern languages existed for a long time. Dragging bad syntax with them and blocking ways to new expressions. Like rust is way more legible and clear as far as syntax goes than c++ which has a lot of context sensitive keywords and special syntax added later in a compatible way.

    • @anon_y_mousse
      @anon_y_mousse Месяц назад +1

      @@redcrafterlppa303 I'm assuming you meant that sarcastically, because Rust has worse syntax than C++. As bad as C++ is, they've mostly tried to stick with adding more code and not more syntax. They've clearly failed here in multiple ways, which is why they felt invoke and apply were necessary, but they've done better than Rust for sure.

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

      @anon_y_mousse that's a matter of taste. I know both languages pretty well and think rust has better syntax.

  • @01Lic
    @01Lic Месяц назад

    So, in few years, this is the kind of syntax we will give AI to automatically write software for us and then, after a short while, we will wonder why we don't understand a thing of what it is doing anymore.

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










  • @Subdest
    @Subdest Месяц назад +2

    5:54 but it is std::forward not straight::forward :)

  • @yntfwyk
    @yntfwyk Месяц назад +1

    Instead of std::remove_cvref_t can we use std::decay_t?

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

      yeah, probably. Though, decay_t is designed to decay C-arrays into pointers, and std::remove_cvref doesn't do that

    • @AlfredoCorrea
      @AlfredoCorrea Месяц назад +1

      I use decay because it is easier to type and it has a cool name. And since I don't use C-arrays it is never ambiguous.

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

      @@AlfredoCorrea same

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

      Hey why just remove cv_ref at all? Does the length of the tuple badly computed if we use tuple type as is, being const or volatile possibly reference?

    • @yntfwyk
      @yntfwyk Месяц назад +2

      @@MetaBarj0 The std implementation of tupe_size_v deduces tuple template args using a non-const volatile tuple type.

  • @xorbe2
    @xorbe2 Месяц назад +3

    Param pack syntax seems so non-intuitive.

  • @oleksiistri8429
    @oleksiistri8429 Месяц назад +3

    correct answer: C++ devs like to bloat the language with useless stuff

    • @garyp.7501
      @garyp.7501 Месяц назад +1

      These fns, invoke, apply are really for library writers.

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

      you can use C, it's always allowed 🤷