The way I've always dealt with these types of things, not that it comes up terribly often, is to use a bit pattern type ID for the various objects that need to be matched up, and to bitwise-or them together to switch on their meshed type ID's. This is actually one of those test cases where I know that people haven't read the AMD optimization manual. It has this technique in it to deal with condensing and optimizing giant if/else if/else chains in loops, amongst other uses. If anyone reads this, I'm going to recommend that you read both the Intel and AMD optimization manuals, because they don't merely give tips for writing better assembly, but also for implementing better and more optimized code patterns in all languages. If you can do it without thinking, then you've properly absorbed the lessons within.
@@flemminggramchristensen6292 Ignore the date since most of it is still useful information, but try DDG with "amd software optimization guide". If you see a link to a PDF that has 47414 in its name, click that.
Scott Meyers also covered the topic in his book "More Effective C++". Item 31: Making functions virtual with respect to more than one object. It was very interesting to get a modern take on the subject. Thank-you!
for (const auto &other : get_objects()) { collide(obj, other); } } int main () { for (const auto &obj : get_objects()) process_collisions(obj); return 0; }
So this works when we don't have too many derived types and all are known at compile-time. It's cool. And I think it does not fully replace the first solution you had which was using dynamic polymorphism.
@@fernandoiglesiasg It depends on the wider context. Perhaps make `collide` take a position{int x, int y} object produced from each space object. That makes it single dispatch on the objects and a single version of `collide`?
Aha! That’s interesting, I think I hadn’t considered pattern matching in the context of multiple dispatching. I normally look into Julia doc when want to recall let’s say “multiple dispatching done right”.
Oh that's a really cool pattern. On a personal note, I just don't like the trailing return types, It's just confusing when you see auto and sometimes scroll very far just to find the actual return type. I prefer wiriting everything before the return type on the line above.
I implemented ChaiScript into a game I was creating. Unfortunately it remembers declarations and fails to execute a second+ time. Needs a reset on the chai object. Using Lua + Sol works in the way I expected.
The problem with trailing return types is that if you forget to write the trailing end ( -> CopyableMovableClass&) on a method returning a reference, you will end up copying (because you will have "auto getRefObject()" instead of "auto getRefObject() -> CopyableMovableClass&". If the object has copy constructor all will be compiled without a warning. Too bad that there is not a some kind of warning that user did not write trailing type at least on clang-tidy side.
I'm confused about your concern. If I forget an & on the legacy syntax, it will create a copy. Also, auto& my_func() is valid syntax, which I would argue is exactly as error prone in this respect as the legacy format
@@keris3920 It is not the same, the one you talking about is one problem which can be done even with trailing part. you can also write -> auto instead of a -> auto& but on top of that you can also miss out the trailing part without any warning. I see there two possibilities how to make unintented code instead of one.
@@jakubskopal43 your entire argument rests on the presumption that copies are a bad thing. Copies can often be faster and don't suffer from some of the lifetime issues that references suffer from (e.g. dangling references). Anyway, that's beside the point. The point I'm trying to make, is that regardless of the syntax you use, programmers will always make silly mistakes. I ultimately agree with you that a linter/formatter would help uncover some small bugs in this area. But I could probably list a seemingly infinite amount of bugs that could arise from forgetting parts of a function declaration. At the end of the day, I appreciate your warning, but I don't see trailing return type syntax as the root cause for this flavor of bug.
@@keris3920 I did not say that copies are bad thing. I explicitly said that you want to return reference .. you do not want to copy the object. It does not matter what I think about copies, it is about intent. And the intent was return a reference which can silently result in unintended code because you have two places to make a mistake instead of one.
@@jakubskopal43 organize your functions in the opposite order of which they're used so that the compiler complains about deducing the return type of the function before it's declared. That way you will get a compile error when you forget the return type. Problem solved.
[[nodiscard]] makes more sense to me, and i ve never even used it. [[use]] sounds like a suggestion, not an obligation. Maybe something like [[mustuse]] would have been just as good
The way I've always dealt with these types of things, not that it comes up terribly often, is to use a bit pattern type ID for the various objects that need to be matched up, and to bitwise-or them together to switch on their meshed type ID's. This is actually one of those test cases where I know that people haven't read the AMD optimization manual. It has this technique in it to deal with condensing and optimizing giant if/else if/else chains in loops, amongst other uses.
If anyone reads this, I'm going to recommend that you read both the Intel and AMD optimization manuals, because they don't merely give tips for writing better assembly, but also for implementing better and more optimized code patterns in all languages. If you can do it without thinking, then you've properly absorbed the lessons within.
Where do I find the AMD manual?
@@flemminggramchristensen6292 Ignore the date since most of it is still useful information, but try DDG with "amd software optimization guide". If you see a link to a PDF that has 47414 in its name, click that.
@@flemminggramchristensen6292 Never mind. Apparently TH-cam is just going to eat all of my posts.
Scott Meyers also covered the topic in his book "More Effective C++". Item 31: Making functions virtual with respect to more than one object.
It was very interesting to get a modern take on the subject. Thank-you!
not even a cpp dev but still watching Jason Turner. Keep this up man ❤❤
A very interesting modern implementation of double-dispatch. Thanks.
This was great! I like the pattern and it looks very straight forward.
I've found that using overloaded constructors instead of functions makes the compiler generate less code.
struct collide
{
collide(Craft &A, Craft &B) { A.x += B.y; }
collide(Asteroid &A, Asteroid &B){ A.x += B.y; }
collide(Craft &A, Asteroid &B){ A.x += B.y; }
collide(Asteroid &A, Craft &B){ A.x += B.y; }
};
wow this looks very clean
It generates 'less' code because the free functions must have an address. If you put them in an anonymous namespace it's the same code.
What about:
template
struct collide {
constexpr collide(const T1 &A, const T2 &B) : m_a(A), m_b(B) { }
constexpr operator int() const noexcept {
return result_value;
}
int result_value = 0;
const T1& m_a;
const T2& m_b;
};
Or (edit: it's actually a bit larger)
template
struct collide {
using vtype = std::variant;
collide (const Craft &, const Craft &) noexcept {std::puts("C/C"); }
collide (const Asteroid &, const Asteroid &) noexcept { std::puts("A/A");}
collide (const Craft &, const Asteroid &) noexcept { std::puts("C/A");}
collide (const Asteroid &, const Craft &) noexcept { std::puts("A/C");}
collide(const vtype& v1,const vtype& v2) noexcept {
std::visit(
[this](const auto &lhs, const auto &rhs) {
if (lhs.x == rhs.x && lhs.y == rhs.y) {
collide(lhs, rhs);
}
}
, v1, v2);
}
};
std::vector get_objects()
{
return { {Craft(), Asteroid()} };
}
void process_collisions(const std::variant &obj) {
for (const auto &other : get_objects()) {
collide(obj, other);
}
}
int main ()
{
for (const auto &obj : get_objects())
process_collisions(obj);
return 0;
}
So this works when we don't have too many derived types and all are known at compile-time. It's cool. And I think it does not fully replace the first solution you had which was using dynamic polymorphism.
Very interesting! I hope i never see this in production code :D
Cool trick ^^
Swift enum and its pattern matching is one of the more attractive features of that language
very nice.. although If I needed this in such an example, I would seriously question if I have the right design?
In that case, what design would you suggest for the example?
@@fernandoiglesiasg It depends on the wider context. Perhaps make `collide` take a position{int x, int y} object produced from each space object. That makes it single dispatch on the objects and a single version of `collide`?
To me it's just an argument that we want pattern matching so we get this naturally when we need/want it.
Aha! That’s interesting, I think I hadn’t considered pattern matching in the context of multiple dispatching. I normally look into Julia doc when want to recall let’s say “multiple dispatching done right”.
thanks for the video Jason! It would have been great to see the reasoning why multiple disptach is not natively supported in c++
I think it's the kind of thing we're "waiting for pattern matching for" (if that ever comes...)
Things are only in C++ if they solve real world problems. Not just if they are interesting.
Other early oo languages has it.
Nice
Oh that's a really cool pattern. On a personal note, I just don't like the trailing return types, It's just confusing when you see auto and sometimes scroll very far just to find the actual return type. I prefer wiriting everything before the return type on the line above.
But if you get used to it you don't like the old school way.
This is the kind of thing I'm pretty agnostic on.
would be nice to see if boost::variant2 generates better code than std::variant
I implemented ChaiScript into a game I was creating. Unfortunately it remembers declarations and fails to execute a second+ time. Needs a reset on the chai object. Using Lua + Sol works in the way I expected.
yeah, that was a feature that was considered awhile back but never implemented.
The problem with trailing return types is that if you forget to write the trailing end ( -> CopyableMovableClass&) on a method returning a reference, you will end up copying (because you will have "auto getRefObject()" instead of "auto getRefObject() -> CopyableMovableClass&". If the object has copy constructor all will be compiled without a warning. Too bad that there is not a some kind of warning that user did not write trailing type at least on clang-tidy side.
I'm confused about your concern. If I forget an & on the legacy syntax, it will create a copy. Also, auto& my_func() is valid syntax, which I would argue is exactly as error prone in this respect as the legacy format
@@keris3920 It is not the same, the one you talking about is one problem which can be done even with trailing part. you can also write -> auto instead of a -> auto& but on top of that you can also miss out the trailing part without any warning. I see there two possibilities how to make unintented code instead of one.
@@jakubskopal43 your entire argument rests on the presumption that copies are a bad thing. Copies can often be faster and don't suffer from some of the lifetime issues that references suffer from (e.g. dangling references). Anyway, that's beside the point.
The point I'm trying to make, is that regardless of the syntax you use, programmers will always make silly mistakes. I ultimately agree with you that a linter/formatter would help uncover some small bugs in this area. But I could probably list a seemingly infinite amount of bugs that could arise from forgetting parts of a function declaration. At the end of the day, I appreciate your warning, but I don't see trailing return type syntax as the root cause for this flavor of bug.
@@keris3920 I did not say that copies are bad thing. I explicitly said that you want to return reference .. you do not want to copy the object. It does not matter what I think about copies, it is about intent. And the intent was return a reference which can silently result in unintended code because you have two places to make a mistake instead of one.
@@jakubskopal43 organize your functions in the opposite order of which they're used so that the compiler complains about deducing the return type of the function before it's declared. That way you will get a compile error when you forget the return type. Problem solved.
is this "reflection at home"?
Available starting which version of C++
c++17, I think
std::variant and std::visit were added in 17.
@@AruisDante
@dadisuperman3472
yes 17. But you need the to deduction guide for the "visitor struct" option until c++20
The proper way to do it is to use the visitor pattern
it is so cringy that they used [[nodiscard]] instead of [[use]] because probably 0.0001% of lines of code have variable named use.
[[nodiscard]] makes more sense to me, and i ve never even used it. [[use]] sounds like a suggestion, not an obligation. Maybe something like [[mustuse]] would have been just as good
I can use this in our code but I will get a review comments 😂😂