Date: Sat, 26 Apr 2025 16:05:13 +0200
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).
> * 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.
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.
my 2 c.
Mauro.
Il giorno sab 26 apr 2025 alle ore 15:54 Jennifier Burnett via
Std-Discussion <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]> 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
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>
> * 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).
> * 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.
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.
my 2 c.
Mauro.
Il giorno sab 26 apr 2025 alle ore 15:54 Jennifier Burnett via
Std-Discussion <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]> 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
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>
Received on 2025-04-26 14:05:26