C++ Logo

std-discussion

Advanced search

Re: Guarantees over addresses from function pointers created from lambda

From: Federico Kircheis <federico_at_[hidden]>
Date: Sun, 27 Apr 2025 10:07:33 +0200
On 26/04/2025 9:53 pm, Jens Maurer via Std-Discussion wrote:
>
>
> On 26/04/2025 19.43, Jennifier Burnett via Std-Discussion wrote:
>> Quoting [expr.eq]p2:
>>
>>> Comparing pointers
>> is defined as follows: Two pointers compare equal if they are both null, both point to the same function, or
>> both represent the same address (3.9.2), otherwise they compare unequal
>>
>> Specifically we're interested in "Two pointers compare equal if ... both point to the same function".
>>
>> Given
>>
>> ```
>> void foo(){}
>> void bar(){}
>> ```
>>
>> The only way in which `&foo == &bar` could be true is if you say that both foo and bar are the same function. Since they have different names, that to me would would exclude them from being the same function.
>
> Right, but as was already said on this thread, optimizers
> violate that provision quite often.
>
> Could you demonstrate why &foo and &bar not being the same wold
> matter to start with? They have the same behavior.
>
> Jens

Granted, I started the thread with function pointers created through
lambdas, not functions, and the discussion drifted a bit.

Assuming that it is fine to merge them, wouldn't it be better to the
standard to reflect that?
It seems that currently the standard say one thing, but in practice code
relying on it is not portable or considered as fragile, which seems to
be the worst possible outcome.


As to examples, doesn't the one I made qualify?

If

void foo(){}
void bar(){}

have &foo == &bar, I expect it would make sense that


void foo2(){1+1;}
void bar2(){}

have the same address two, because both functions are functionally
equivalent, and after optimizations they cannot be distinguished anymore.


And that leads to my use-case (leaving launder out and using c-style
cast for legibility)


struct pod{int i;}

void foo2(void* dest, const void* source){ *(int*)(dest) =
*(int*)(source); }
void bar2(void* dest, const void* source){ *(pod*)(dest) =
*(pod*)(source); }

can both be optimized to (assuming that sizeof(int) == sizeof(pod),
which is true on most compilers)

void foo2(void* dest, const void* source){ memcpy(dest, source,
sizeof(int); }
void bar2(void* dest, const void* source){ memcpy(dest, source,
sizeof(int); }

Thus foo2 and bar2 are different function (with different bodies), but
after optimizations are the same (which is what the linker cares about).


Right now I'm realizing that maybe(?)

----
     nontrivial_storage& operator=(const nontrivial_storage& other) {
         if (&other != this) {
             if(other.f.assign_copy != this->f.assign_copy){
                 f.destroy(this->buffer);
                 other.f.copy(this->buffer, other->buffer);
             }else{
                 this->f.assign_copy(this->buffer, other.buffer);
             }
             this->f = other.f;
         }
         return *this;
     }
----
is not broken as I thought if foo2 and bar2 compare equals, because 
where such optimization happens the rules for implicit lifetimes would 
kick in.
But what Nate wrote about sentinel values still holds, thus if the 
standard changes and allows different function to have the same address 
(to allow current practice) it would be nice if there was a way to 
opt-out from this behavior.

Received on 2025-04-27 08:07:43