Vogelsgesang
Vogelsgesang

Reputation: 808

Applying [[no_unique_address]] to scope guards within function body

I want to use a stateless scope guard

struct ScopeGuard {
    ScopeGuard();
    ~ScopeGuard();
};

void f();

int test() {
    ScopeGuard scopeguard1;
    ScopeGuard scopeguard2;
    // doesn't work:
    // [[no_unique_address]] ScopeGuard scopeguard3;
    f();
}

The scope guard itself has no state, I am only interested in the side effects of the constructor/destructor.

I want test to have a stack size of 0. But scopeguard1 and scopeguard2 take 8 bytes of stack space each. This is because the this pointer needs to be unique for each instance of ScopeGuard, even if it is stateless.

Usually, I would want to use [[no_unique_address]] in this situation. But it seems [[no_unique_address]] only works for member variables but not for local variables inside functions.

Is there some other way to achieve this? Why is [[no_unique_address]] only supported for member variables? Was this an oversight in C++20? Was it intentional? Would there be any issues with allowing this attribute also for local variables?

Upvotes: 0

Views: 110

Answers (1)

Nicol Bolas
Nicol Bolas

Reputation: 474436

I want test to have a stack size of 0. But scopeguard1 and scopeguard2 take 8 bytes of stack space each. This is because the this pointer needs to be unique for each instance of ScopeGuard, even if it is stateless.

How do you know that they don't?

If the compiler can see that nothing in ScopeGuard actually cares about the value of the this pointer, and nobody actually tries to take the address of a stack variable of this type, then the compiler is 100% free to not give such a variable space on the stack.

Also, you've misunderstood part of the behavior of no_unique_address. Despite the name, it actually doesn't allow two instances of the same type to have the same address. It allows two instances of different types to overlap in member layouts. So even if you could declare those local variables with that attribute, the compiler could not given them the same address (assuming someone looked at the address at all).

no_unique_address was needed for member variables because C++ has specific rules for how a type is laid out, and each member needs its own region of storage distinct from all others. no_unique_address allowed members to break that rule, so long as the unique identity rule is satisfied.

For local variables, the compiler has the option to optimize the stack as it sees fit, so it doesn't need no_unique_address to give it permission.

And don't forget: no_unique_address doesn't provide any guarantees of anything either. It gives the compiler the option to optimize something away; it's never mandatory.

Upvotes: 4

Related Questions