Using the macros in the second linked file, this expands to:
auto _defer_instance_1234 = Defer{} % [&]()->void { CoUninitialize(); };
* The 1234 is whatever the line number is, which makes the variable name unique.
* auto means infer the type of this local variable from the expression after the =.
* Defer{} means default construct a Defer instance. Defer is an empty type, but it allows the % following it to call a specific function because...
* Defer has an overloaded operator%. It's a template function, which takes a callable object (type is the template parameter Callable) and returns a DeferHolder<Callable> instance.
* [&]()->void { /*code here*/ }; is C++ syntax for a lambda function that captures any variables it uses by address (that's the [&] bit), takes no parameters (that's the () bit) and returns nothing (that's the ->void bit). The code goes in braces.
* DeferHolder calls the function it holds when it is destroyed.
It's subjective but some (including me!) would say it's cursed because it's using a macro to make something that almost looks like C++ syntax but isn't quite. I'm pretty confident with C++ but I had no idea what was going on at first (except, "surely this is using macros somehow ... right?"). [Edit: After some thought, I think the most confusing aspect is that defer->void looks like a method call through an object pointer rather than a trailing return type.]
I'd say it would be better to just be honest about its macroness, and also just do the extra typing of the [&] each time so the syntax of the lambda is all together. (You could then also simplify the implementation.) You end up with something like this:
DEFER([&]()->void { CoUninitialize(); });
Or if you go all in with no args lambda, you could shorten it to:
That's interesting! So i assume that this macro allows code to get registered to be run after the 'current' scope exits.
But from my understanding (or lack thereof), the `auto _defer_instance_1234 =` is never referenced post construction. Why doesn't the compiler immediately detect that this object is unused and thus optimize away the object as soon as possible? Is it always guaranteed that the destructor gets called only after the current scope exits?
> Why doesn't the compiler immediately detect that this object is unused and thus optimize away the object as soon as possible? Is it always guaranteed that the destructor gets called only after the current scope exits?
Yes, exactly. The destructor is allowed to have some visible side effect such as closing a file handle or unlocking a mutex that could violate the assumption of the code in that block. (Even just freeing some memory could be an issue for code in the block.) It is guaranteed that the destructor is closed at the end of the block, and that all the destructors called in that way happen in reverse order to the order of their corresponding constructors.
I don't think we actually need `->void` -- shouldn't the compiler be able to infer the return type (or rather, absence thereof)? My experience is that the compiler only struggles when the return value needs to be implicitly converted to some other type.
Would it have looked any less cursed if it just read `defer { CoUninitialize(); };`?
Agreed that the simplest "fix" would be to just rename the macro to be all-caps.
> Would it have looked any less cursed if it just read `defer { CoUninitialize(); };`?
It's subjective but personally I still hate it.
> Agreed that the simplest "fix" would be to just rename the macro to be all-caps.
Actually I think the bigger part of my suggestion is switching from an object-like macro to a function-like macro [1], which makes it all a bit less magical.
And, I personally hate macros that pretend to be functions but provide no visual indicator that they're not actually functions. For instance, `#define min(x, y) (x < y ? x : y)` evaluates its args multiple times. It's a little less bad when it only takes a single argument, but I am still irritated by things like htonl.
I think the "best" approach here would be to make it a function-like macro, and also change the name to all caps.
(Also, I tend to agree that `defer { ... };` is still cursed -- it requires the trailing semicolon, which further breaks the illusion of a keyword that takes a block scope.)
> * Defer has an overloaded operator%. It's a template function, which takes a callable object (type is the template parameter Callable) and returns a DeferHolder<Callable> instance.
Is there any reason to use operator% instead of a normal method call? Except possibly looking cool, which doesn't seem useful given that the call is hidden away in a macro anyway.
If you used a normal method call then there would need to be a corresponding close bracket at the end of the overall line of code, after the end of the lambda function. But the macro ("defer") only occurs at the start of the line, so it has no way to supply that close bracket. So the caller of the macro would have to supply it themselves. As I mentioned near the end of my comment, it seems like the defer macro is specifically engineered to avoid the caller needing a close bracket.
If you don't mind that, I said that you can "simplify the implementation" - what I meant was, as you say, you don't need the overloaded Defer::operator% (or indeed the Defer class at all). Instead you could do:
Eh, there are better implementations that are less syntactically obtuse (no ->void) but other than that it’s fine. Fairly obvious what it’s supposed to do and I’ve needed similar things in the past. There’s a cppcon talk that use ->* operator for precedence reasons and the macro lets you use it like ‘defer { … };’
* auto means infer the type of this local variable from the expression after the =.
* Defer{} means default construct a Defer instance. Defer is an empty type, but it allows the % following it to call a specific function because...
* Defer has an overloaded operator%. It's a template function, which takes a callable object (type is the template parameter Callable) and returns a DeferHolder<Callable> instance.
* [&]()->void { /*code here*/ }; is C++ syntax for a lambda function that captures any variables it uses by address (that's the [&] bit), takes no parameters (that's the () bit) and returns nothing (that's the ->void bit). The code goes in braces.
* DeferHolder calls the function it holds when it is destroyed.
It's subjective but some (including me!) would say it's cursed because it's using a macro to make something that almost looks like C++ syntax but isn't quite. I'm pretty confident with C++ but I had no idea what was going on at first (except, "surely this is using macros somehow ... right?"). [Edit: After some thought, I think the most confusing aspect is that defer->void looks like a method call through an object pointer rather than a trailing return type.]
I'd say it would be better to just be honest about its macroness, and also just do the extra typing of the [&] each time so the syntax of the lambda is all together. (You could then also simplify the implementation.) You end up with something like this:
Or if you go all in with no args lambda, you could shorten it to: