Hey folks,
It's not great that the caller syntax is a weird single member structure binding, but the code generation for this case is optimal (only because the functor has no data members and everything is inlined though).
Bit out there, but I can see this trick could be used in a real codebase.
Heh. Okay, that's very cute. :)
It took me a moment to see the trick — and there is a trick! It's that your `getLockedMutex()` function doesn't return a mutex at all; it returns an `ImWrapper<std::mutex, lambda>` which can be destructured on the caller's side with `auto [m] = getLockedMutex()`. But, if the caller decides not to destructure the return value (yet), then the lock never happens. And the lock happens in the caller and after the `getLockedMutex()` function is finished executing, as opposed to inside `getLockedMutex()` during its evaluation.
So the codegen inside `main` is equivalent to
struct { std::mutex m; } invisible = getLockedMutex(); // call the function, which returns a plain old unlocked mutex wrapped in a struct
invisible.m.lock(); // this is the effect of calling `get<0>`
std::mutex& m = invisible.m; // this is the new name introduced by the structured binding
[...now use `m` as usual...]
Another cheating option — i.e. if you don't mind that the returned mutex must be immediately stored into a named variable — would be to pass the return slot's own address as an accessible parameter to `getLockedMutex` itself, like this:
auto getLockedMutex(std::mutex *pm) {
Auto(pm->lock());
return std::mutex();
}
int main() {
std::mutex m = getLockedMutex(&m);
m.unlock();
}
where `Auto` is
the `Auto` macro. So we construct a `std::mutex` into the return slot and then (as the last step inside `getLockedMutex`) we lock it.
This is even kinda slightly less cheating, because the lock happens inside `getLockedMutex`. But it's still UB (I bet taking `&m` in its own initializer is verboten), and it's still cheating because it doesn't work in a standalone expression such as `unlockMutex(getLockedMutex())`.
Cheers,
Arthur