Date: Sat, 26 Apr 2025 14:41:51 +0200
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":
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 12:41:57