August 2, 2024
cout << "hello, world" << endl;
This made the C programmer in me squirm. Why would you make the meaning of operators change wildly based on the context? Also you might lose track of what code is actually being generated. It could have side effects that you don't know about, or be orders of magnitude slower! I still fill this way, and avoid operator overloading, other than operator=, for most things.
...but having said that, I find operator overloading to be incredibly valuable when it comes to maintaining and refactoring a large code base. A pattern that has become routine for us is a basic type is initially used for some state and needs to be extended.
For example: in track groups, we originally had 32 groups (1-32), and a track could have different types of membership in any number of groups. For 32 groups, we used unsigned ints as bitmasks. Some years later, to support 64 groups we changed it to WDL_UINT64 (aka uint64_t). Straightforward (but actually quite tedious and in hindsight we should've skipped the type change and gone right to the next step). To increase beyond 64 bits, there's no longer a basic type that can be used. So instead:
struct group_membership { enum { SZ = 2 }; WDL_UINT64 m_data[SZ]; const group_membership operator & (const group_membership &o) const { group_membership r; for (int i = 0; i < SZ; i ++) r.m_data[i] = m_data[i] & o.m_data[i]; return r; } // and a bunch of other operators, a couple of functions to clear/set common masks, etc private: // prevent direct cast to int/int64/etc. necessary because we allow casting to bool below which would otherwise be quietly promoted to int operator int() const { return 0; } public: operator bool() const { for (int i = 0; i < SZ; i ++) if (m_data[i]) return true; return false; } };
Then we replace things like:
WDL_UINT64 group;with
group_membership group;
and after that, (assuming you get all of the necessary operators mentioned in the comment above) most code just works without modification, e.g.:
if (group & group_mask) { /* ... */ }
To be fair, we could easy tweak all of the code that the operator overloading implements to use functions, and not do this, but for me knowing that I'm not screwing up the logic in some subtle way is a big win. And if you have multiple branches and you're worried about merge conflicts, this avoids a lot of them.
Also, it's fun to look at the output of gcc or clang on these things. They end up producing pretty much optimal code, even when returning and copying structs. Though you should be sure to keep the struct in question as plain-old-data (ideally no constructor, or a trivial constructor, and no destructor).
Thus concludes my "a thing I appreciate about C++" talk. Next week, templates.
Recordings:
super8_novideo - 1 -- [9:36]
super8_novideo - 2 -- [4:16]
super8_novideo - 3 -- [4:34]
super8_novideo - 4 -- [8:20]
Posted by wasereb4 on Thu 22 Aug 2024 at 19:51 from 95.90.106.x
Posted by Justin on Sat 24 Aug 2024 at 14:10 from 71.125.233.x
Posted by Kiernan Holland on Thu 05 Sep 2024 at 12:05 from 47.185.56.x
Posted by Kiernan Holland on Thu 05 Sep 2024 at 12:11 from 47.185.56.x
Posted by same guy on Thu 05 Sep 2024 at 12:24 from 47.185.56.x
Posted by oops on Thu 05 Sep 2024 at 12:40 from 47.185.56.x
Posted by kjh on Thu 05 Sep 2024 at 12:44 from 47.185.56.x
Posted by last one on Thu 05 Sep 2024 at 12:53 from 47.185.56.x
Posted by okay one more on Thu 05 Sep 2024 at 13:08 from 47.185.56.x
Posted by UDIO music TV channel , enjoy.. on Thu 05 Sep 2024 at 13:16 from 47.185.56.x
Posted by yes on Thu 05 Sep 2024 at 13:43 from 47.185.56.x
Posted by Justin on Fri 06 Sep 2024 at 21:47 from 71.125.233.x
Add comment: