November 20, 2024
$nwin = 250; # I think the lottery picked about 250-something winners last year, the rest were golden tickets and such $nlot = 100000; # someone could quantify the margin of error based on this, with statistics you would oh look a bird for ($pid = 0; <>; ) { ($nt, $np) = split(/\s+/, s/,//r); ($nt > 0 and $np > 0 and not exists $wcnt{$nt}) or die "invalid input: $_\n"; $wbin{$pid} = $nt; $wcnt{$nt} = 0; push(@tk, ($pid++) x $nt) while ($np-- > 0); } printf("%d tickets for %d entrants, running %d lotteries for %d winners:\n", $tkcnt = @tk, $pid, $nlot, $nwin); for ($x = 0; $x < $nlot; $x++) { %in = { }; $in{$id = $tk[rand($tkcnt)]}++ == 0 and exists $wbin{$id} and $wcnt{$wbin{$id}}++ while (%in < $nwin); } printf("%d tickets: %.2f%% win\n", $_, $wcnt{$_} * 100.0 / $nlot) foreach (sort { $a <=> $b } keys %wcnt);Here is the most recent input:
512 1 512 256 15 3,840 128 55 7,040 64 139 8,896 32 215 6,880 16 296 4,736 8 594 4,752 4 963 3,852 2 1,538 3,076 1 2,077 2,077and here is the most output with that table:
45661 tickets for 5893 entrants, running 100000 lotteries for 250 winners: 1 tickets: 0.66% win 2 tickets: 1.29% win 4 tickets: 2.56% win 8 tickets: 5.08% win 16 tickets: 9.99% win 32 tickets: 18.98% win 64 tickets: 34.12% win 128 tickets: 56.51% win 256 tickets: 80.91% win 512 tickets: 96.24% winSo, Steve's odds as of this afternoon are about 0.66%, but that will almost certainly go down (there's still a month left of the lottery; it only opened yesterday). Interestingly, one entrant there has been turned down 8 times before -- they currently have a 96% chance of getting in. And those who have been turnwed down 6 times before are slightly more likely than not to get in.
#include <windows.h> int WINAPI WinMain(HINSTANCE hInst, HINSTANCE prev, LPSTR cmd, int ns) { const int w = 32, h = 16; int pixels[w*h]; for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) pixels[x + y * w] = RGB(x * 256 / w, y * 256 / h, 0); // oops BGR but whatever HBITMAP bitmap = CreateBitmap(w, h, 1, 32, &pixels); HWND wnd = CreateWindowEx(0, "Edit", "test", WS_VISIBLE, 0, 0, 10, 10, GetDesktopWindow(), NULL, hInst, 0); HMENU menu = CreatePopupMenu(); InsertMenuA(menu, 0, MF_BYPOSITION|MF_STRING, 1, (char*)"this is a thing that will cause stretching to happen"); InsertMenuA(menu, 1, MF_BYPOSITION|MF_BITMAP, 2, (char*)bitmap); TrackPopupMenu(menu, TPM_NONOTIFY|TPM_RETURNCMD, 0, 0, 0, wnd, NULL); DestroyMenu(menu); DeleteObject(bitmap); DestroyWindow(wnd); return 0; }Windows XP, Windows 7, and WINE all display something about like this, which is expected:
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]
12 Comments