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...
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.
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
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.
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
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.
@@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
@@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...
@ 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?
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.
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!)
@ 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?
@@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
@@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.
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
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
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).
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.
@@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.
@@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.
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.
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.
@@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.
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.
I'm casting my vote in favor of more template metaprogramming on this channel! Very cool stuff.
Ditto
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...
I actually find this type of programming very fascinating. Would love to see more of it on this channel!
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.
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
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.
boost lambda library does this. (stores the arguments and nested fn's in tuples)
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
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.
@@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
@@AlfredoCorrea idk, making a basic recursive tuple was the easiest part, imo
@@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...
@ 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?
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 :)
std::invoke & std::apply do take by reference, it's only the algorithms that take callables by value.
Also they are newer than the algorithms which cannot be upgraded to use references for backward compatible I suppose.
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.
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.
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!)
@ 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?
@@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
@@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.
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
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
you can already turn trivial structs into tuples using c++17 structured bindings, so yeah, that's a thing
i struggle loooooooooong time on std::index_sequence, this is probably the first time I get an idea "why" it is there
It is better to encounter this with a concise explanation then having to decode this cold in some bit of code without any hints.
I wonder why there isn't an overloaded version of invoke that takes a touple
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).
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
Let's hope that C++ reflections replace most of the variadic expansion code, so we get rid of the cryptic ellipses.
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.
@@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.
@@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.
That’s certainly… something…
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.
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.
@@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.
@anon_y_mousse that's a matter of taste. I know both languages pretty well and think rust has better syntax.
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.
高
山
仰
止
,
景
行
行
止
。
5:54 but it is std::forward not straight::forward :)
Instead of std::remove_cvref_t can we use std::decay_t?
yeah, probably. Though, decay_t is designed to decay C-arrays into pointers, and std::remove_cvref doesn't do that
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.
@@AlfredoCorrea same
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?
@@MetaBarj0 The std implementation of tupe_size_v deduces tuple template args using a non-const volatile tuple type.
Param pack syntax seems so non-intuitive.
correct answer: C++ devs like to bloat the language with useless stuff
These fns, invoke, apply are really for library writers.
you can use C, it's always allowed 🤷