Date: Sat, 26 Apr 2025 18:43:06 +0100
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.
On 26 April 2025 17:22:42 BST, Federico Kircheis via Std-Discussion <std-discussion_at_[hidden]> wrote:
>On 26/04/2025 4:05 pm, mauro russo wrote:
>> from the original questions:
>>
>> > * if the same function can have different addresses, the class is
>> > inefficient (destructing and creating objects instead of assigning them)
>>
>> Jennifer provided already an answer: different template parameters,
>> means different instantiations, therefore distinct closure types (about lambda, the same signature leads anyway to distinct types).
>
>Yes, but the function pointers have the same type.
>
>The point that I was trying to make, is that in case of trivial type, the following snippet
>
>.copy = [](void* dest, const void* source) { ::new (dest) T(*std::launder(reinterpret_cast<const T*>(source)));},
>
>is equivalent to
>
>.copy = [](void* dest, const void* source) { std::memcpy(dest, source, sizeof(T);}
>
>Since sizeof(T) is just a constant number, the body of the lambda does not depend on T anymore.
>
>> > * if different function can have the same address, the class if
>> > broken, (the lifetime of the previous object did not end correctly)
>>
>> This should be something never happening. It cannot make sense
>> to have different functions at the same address.
>
>IMHO it makes sense that
>
>void foo(){}
>void bar(){}
>
>can have the same address, similarly to how identical string literals can have the same address; I was not sure if such optimization was allowed.
>
>> Anyway, in your draft code, please consider that you are assigning, for example, to f.copy a local lambda whose lifetime ends when your constructor ends executing.
>
>
>Mhm, f.copy is assigned to a function pointer created through a lambda, not a pointer to the lambda.
>It was my understanding that those functions have global lifetimes, like static functions.
>
>> my 2 c.
>> Mauro.
>>
>> Il giorno sab 26 apr 2025 alle ore 15:54 Jennifier Burnett via Std- Discussion <std-discussion_at_[hidden] <mailto:std- discussion_at_[hidden]>> ha scritto:
>>
>> To simplify this question a bit, given how the standard defines
>> lambdas you are asking if given the following:
>>
>> ```
>> struct A
>> {
>> static void foo() {}
>> };
>> struct B
>> {
>> static void foo() {}
>> }
>> ```
>>
>> If it is possible that `&A::foo == &B::foo` can return `true`.
>>
>> Relevant transformations are in [expr.prim.lambda]p6, plus using the
>> fact that instantiations of a templated type with different template
>> parameters are distinct types.
>>
>>
>>
>>
>>
>> On 26 April 2025 13:41:51 BST, Federico Kircheis via Std-Discussion
>> <std-discussion_at_[hidden] <mailto:std-
>> discussion_at_[hidden]>> wrote:
>> >Hello,
>> >
>> >I'm trying to understand if the standard does guarantee anything
>> about the uniqueness of function pointers created by lambda (thus
>> lambdas with empty captures).
>> >
>> >In particular, given a template factory that generates functions
>> through a lambda (the template parameter is used inside the lambda,
>> no capture takes place):
>> >
>> >1) are the function pointers the same for the same template parameter?
>> >2) if the template parameter does not affect the generated
>> assembly, can two functions point to the same addresses?
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >In case anyone is wondering why I'm interested in those
>> properties, or my question is unclear and you are wondering what I
>> really want to know...
>> >
>> >
>> >Consider the following class "nontrivial_storage":
>> >
>> >
>> >----
>> >template <int N>
>> >class nontrivial_storage {
>> > alignas(N) unsigned char buffer[N] = {};
>> >
>> > struct {
>> > using copy_fun_t = void(void*, const void*);
>> > using assign_copy_fun_t = void(void*, const void*);
>> > using move_fun_t = void(void*, void*) noexcept;
>> > using assign_move_fun_t = void(void*, void*) noexcept;
>> > using dest_fun_t = void(void*) noexcept;
>> > copy_fun_t* copy;
>> > assign_copy_fun_t* assign_copy;
>> > move_fun_t* move;
>> > assign_move_fun_t* assign_move;
>> > dest_fun_t* destroy;
>> > } f;
>> >
>> > public:
>> > template <typename T>
>> > explicit nontrivial_storage(const T& t) noexcept :
>> > f{
>> > .copy = [](void* dest, const void* source) { ::new
>> (dest) T(*std::launder(reinterpret_cast<const T*>(source)));},
>> > .assign_copy = [](void* dest, const void* source)
>> {*std::launder(reinterpret_cast<T*>(dest)) =
>> *std::launder(reinterpret_cast<const T*>(source));},
>> > .move = [](void* dest, void* source) noexcept
>> { ::new (dest)
>> T(std::move(*std::launder(reinterpret_cast<T*>(source))));},
>> > .assign_move = [](void* dest, void* source) noexcept
>> {*std::launder(reinterpret_cast<T*>(dest)) =
>> std::move(*std::launder(reinterpret_cast<const T*>(source)));},
>> > .destroy = [](void* dest) noexcept
>> { std::launder(reinterpret_cast<T*>(dest))->~T(); },
>> > } {
>> > f.copy(buffer, &t);
>> > static_assert(std::is_nothrow_move_constructible_v<T>,
>> "otherwise move op be noexcept(false)");
>> > }
>> > nontrivial_storage(const nontrivial_storage& other) : f(other.f) {
>> > f.copy(this->buffer, other->buffer);
>> > }
>> > 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;
>> > }
>> >
>> > // move operations and destruction
>> > // ...
>> >
>> >};
>> >----
>> >
>> >It uses "if(other.f.assign_copy != this->f.assign_copy)" to
>> determine if the contained type-erased type is the same without
>> storing anywhere information about the type (for example with
>> type_info).
>> >But if the "same function" can have different addresses or (worse)
>> different functions can have the same address, then it this class is
>> problematic.
>> >
>> >
>> >(My) experience shows that
>> >
>> >[](void* dest, const void* source)
>> {*std::launder(reinterpret_cast<T*>(dest)) =
>> *std::launder(reinterpret_cast<const T*>(source));}
>> >
>> >generates the same function address for the same template, and
>> different addresses for different templates.
>> >
>> >A valid implementation of the lambda could "just" memcpy sizeof(T)
>> bytes (for example if T is an integral type), thus for different
>> templates, the function body would be 100% equivalent (from a
>> compiler POV).
>> >
>> >Thus:
>> >
>> > * if the same function can have different addresses, the class is
>> inefficient (destructing and creating objects instead of assigning them)
>> > * if different function can have the same address, the class if
>> broken, (the lifetime of the previous object did not end correctly)
>> >
>> >
>> >Best
>> >
>> >Federico
>
> 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.
On 26 April 2025 17:22:42 BST, Federico Kircheis via Std-Discussion <std-discussion_at_[hidden]> wrote:
>On 26/04/2025 4:05 pm, mauro russo wrote:
>> from the original questions:
>>
>> > * if the same function can have different addresses, the class is
>> > inefficient (destructing and creating objects instead of assigning them)
>>
>> Jennifer provided already an answer: different template parameters,
>> means different instantiations, therefore distinct closure types (about lambda, the same signature leads anyway to distinct types).
>
>Yes, but the function pointers have the same type.
>
>The point that I was trying to make, is that in case of trivial type, the following snippet
>
>.copy = [](void* dest, const void* source) { ::new (dest) T(*std::launder(reinterpret_cast<const T*>(source)));},
>
>is equivalent to
>
>.copy = [](void* dest, const void* source) { std::memcpy(dest, source, sizeof(T);}
>
>Since sizeof(T) is just a constant number, the body of the lambda does not depend on T anymore.
>
>> > * if different function can have the same address, the class if
>> > broken, (the lifetime of the previous object did not end correctly)
>>
>> This should be something never happening. It cannot make sense
>> to have different functions at the same address.
>
>IMHO it makes sense that
>
>void foo(){}
>void bar(){}
>
>can have the same address, similarly to how identical string literals can have the same address; I was not sure if such optimization was allowed.
>
>> Anyway, in your draft code, please consider that you are assigning, for example, to f.copy a local lambda whose lifetime ends when your constructor ends executing.
>
>
>Mhm, f.copy is assigned to a function pointer created through a lambda, not a pointer to the lambda.
>It was my understanding that those functions have global lifetimes, like static functions.
>
>> my 2 c.
>> Mauro.
>>
>> Il giorno sab 26 apr 2025 alle ore 15:54 Jennifier Burnett via Std- Discussion <std-discussion_at_[hidden] <mailto:std- discussion_at_[hidden]>> ha scritto:
>>
>> To simplify this question a bit, given how the standard defines
>> lambdas you are asking if given the following:
>>
>> ```
>> struct A
>> {
>> static void foo() {}
>> };
>> struct B
>> {
>> static void foo() {}
>> }
>> ```
>>
>> If it is possible that `&A::foo == &B::foo` can return `true`.
>>
>> Relevant transformations are in [expr.prim.lambda]p6, plus using the
>> fact that instantiations of a templated type with different template
>> parameters are distinct types.
>>
>>
>>
>>
>>
>> On 26 April 2025 13:41:51 BST, Federico Kircheis via Std-Discussion
>> <std-discussion_at_[hidden] <mailto:std-
>> discussion_at_[hidden]>> wrote:
>> >Hello,
>> >
>> >I'm trying to understand if the standard does guarantee anything
>> about the uniqueness of function pointers created by lambda (thus
>> lambdas with empty captures).
>> >
>> >In particular, given a template factory that generates functions
>> through a lambda (the template parameter is used inside the lambda,
>> no capture takes place):
>> >
>> >1) are the function pointers the same for the same template parameter?
>> >2) if the template parameter does not affect the generated
>> assembly, can two functions point to the same addresses?
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >
>> >In case anyone is wondering why I'm interested in those
>> properties, or my question is unclear and you are wondering what I
>> really want to know...
>> >
>> >
>> >Consider the following class "nontrivial_storage":
>> >
>> >
>> >----
>> >template <int N>
>> >class nontrivial_storage {
>> > alignas(N) unsigned char buffer[N] = {};
>> >
>> > struct {
>> > using copy_fun_t = void(void*, const void*);
>> > using assign_copy_fun_t = void(void*, const void*);
>> > using move_fun_t = void(void*, void*) noexcept;
>> > using assign_move_fun_t = void(void*, void*) noexcept;
>> > using dest_fun_t = void(void*) noexcept;
>> > copy_fun_t* copy;
>> > assign_copy_fun_t* assign_copy;
>> > move_fun_t* move;
>> > assign_move_fun_t* assign_move;
>> > dest_fun_t* destroy;
>> > } f;
>> >
>> > public:
>> > template <typename T>
>> > explicit nontrivial_storage(const T& t) noexcept :
>> > f{
>> > .copy = [](void* dest, const void* source) { ::new
>> (dest) T(*std::launder(reinterpret_cast<const T*>(source)));},
>> > .assign_copy = [](void* dest, const void* source)
>> {*std::launder(reinterpret_cast<T*>(dest)) =
>> *std::launder(reinterpret_cast<const T*>(source));},
>> > .move = [](void* dest, void* source) noexcept
>> { ::new (dest)
>> T(std::move(*std::launder(reinterpret_cast<T*>(source))));},
>> > .assign_move = [](void* dest, void* source) noexcept
>> {*std::launder(reinterpret_cast<T*>(dest)) =
>> std::move(*std::launder(reinterpret_cast<const T*>(source)));},
>> > .destroy = [](void* dest) noexcept
>> { std::launder(reinterpret_cast<T*>(dest))->~T(); },
>> > } {
>> > f.copy(buffer, &t);
>> > static_assert(std::is_nothrow_move_constructible_v<T>,
>> "otherwise move op be noexcept(false)");
>> > }
>> > nontrivial_storage(const nontrivial_storage& other) : f(other.f) {
>> > f.copy(this->buffer, other->buffer);
>> > }
>> > 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;
>> > }
>> >
>> > // move operations and destruction
>> > // ...
>> >
>> >};
>> >----
>> >
>> >It uses "if(other.f.assign_copy != this->f.assign_copy)" to
>> determine if the contained type-erased type is the same without
>> storing anywhere information about the type (for example with
>> type_info).
>> >But if the "same function" can have different addresses or (worse)
>> different functions can have the same address, then it this class is
>> problematic.
>> >
>> >
>> >(My) experience shows that
>> >
>> >[](void* dest, const void* source)
>> {*std::launder(reinterpret_cast<T*>(dest)) =
>> *std::launder(reinterpret_cast<const T*>(source));}
>> >
>> >generates the same function address for the same template, and
>> different addresses for different templates.
>> >
>> >A valid implementation of the lambda could "just" memcpy sizeof(T)
>> bytes (for example if T is an integral type), thus for different
>> templates, the function body would be 100% equivalent (from a
>> compiler POV).
>> >
>> >Thus:
>> >
>> > * if the same function can have different addresses, the class is
>> inefficient (destructing and creating objects instead of assigning them)
>> > * if different function can have the same address, the class if
>> broken, (the lifetime of the previous object did not end correctly)
>> >
>> >
>> >Best
>> >
>> >Federico
>
Received on 2025-04-26 17:43:14