On Tue, May 21, 2024 at 11:04 AM Breno Guimarães via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
Hey folks,

If you're willing to abuse structured binding syntax, you can inject a post-initialization function in get<0>, like this: https://godbolt.org/z/qa1n8a5x5
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. :)
(Slightly tweaked, mostly for style but also to unreverse Fn and Args in one place: https://godbolt.org/z/eGzGKfo4r )
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:
// https://godbolt.org/z/GvKP8rbf4

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