C++ Logo

std-proposals

Advanced search

Re: [std-proposals] std::elide

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Tue, 21 May 2024 22:59:59 -0400
On Tue, May 21, 2024 at 11:04 AM Breno Guimarães via Std-Proposals <
std-proposals_at_[hidden]> 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
<https://quuxplusone.github.io/blog/2018/08/11/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

Received on 2024-05-22 03:00:14