Can two different lambdas (that have no capture, and the same args and body) decay to the same function pointer?
I have this code:
#include <cassert>
#include <type_traits>
int main() {
auto f0 = [](auto x) { return x; };
auto f1 = [](auto x) { return x; };
static_assert(not std::is_same_v<decltype(f0), decltype(f1)>);
// MSVC Release-mode combines the functions so the pointers are the same (even though the types should be different.
assert(static_cast<int(*)(int)>(f0) != static_cast<int(*)(int)>(f1));
}
https://godbolt.org/z/P3vc45654
I believe the static_assert
is guaranteed to pass. Is that assert
guaranteed to pass? (I'm seeing MSVC in release mode failing the assert
on my computers.)
I have to disagree with the existing answers.
You're not using the function call operator. You're using the conversion to "pointer to function". Since your lambdas have auto
parameters, they're generic lambdas.
The conversion to "pointer to function" in this case is described as follows (N4950, [expr.prim.lambda.closure]/9):
For a generic lambda with no lambda-capture, the closure type has a conversion function template to pointer to function. The conversion function template has the same invented template parameter list, and the pointer to function has the same parameter types, as the function call operator template. The return type of the pointer to function shall behave as if it were a decltype-specifier denoting the return type of the corresponding function call operator template specialization.
Nothing here says anything about creating pointers to unique functions, or anything similar.
Microsoft's implementation does not seem to violate any rules here.
I think that this question is related more to Visual Studio build process peculiarities, because constant expression check in Visual Studio compiler correctly proves that it considers two function pointers as distinct:
constexpr auto p0 = static_cast<int(*)(int)>(f0);
constexpr auto p1 = static_cast<int(*)(int)>(f1);
// passes in all compilers, including MSVC
static_assert( p0 != p1 );
Online demo: https://gcc.godbolt.org/z/Msb3zTPjz
Please note that same address issue can be observed not only with generic lambdas, but also with ordinary closure objects and simply with plain functions. In the most reduced form it can be presented as follows:
void f0() {}
void f1() {}
void(*p0)();
void(*p1)();
int main() {
p0 = f0;
p1 = f1;
// returns 1 in GCC and Clang, and MSVC debug mode
// returns 0 in MSVC release mode
return( p0 != p1 );
}
The assembly that Visual Studio produces is actually correct in the sense that the compiler truly compares function pointers on equality:
void (__cdecl* p0)(void) DQ 01H DUP (?) ; p0
void (__cdecl* p1)(void) DQ 01H DUP (?) ; p1
void f0(void) PROC ; f0, COMDAT
ret 0
void f0(void) ENDP ; f0
void f1(void) PROC ; f1, COMDAT
ret 0
void f1(void) ENDP ; f1
main PROC ; COMDAT
lea rdx, OFFSET FLAT:void f0(void) ; f0
xor eax, eax
lea rcx, OFFSET FLAT:void f1(void) ; f1
mov QWORD PTR void (__cdecl* p0)(void), rdx ; p0
cmp rdx, rcx
mov QWORD PTR void (__cdecl* p1)(void), rcx ; p1
setne al
ret 0
main ENDP
Online demo: https://gcc.godbolt.org/z/Mc5qnKzx3
It is the linker that combines two functions into one due to the option /OPT:ICF
enabled by default in Release builds.
There is the warning as follows in the manual:
Because /OPT:ICF can cause the same address to be assigned to different functions or read-only data members (that is, const variables when compiled by using /Gy), it can break a program that depends on unique addresses for functions or read-only data members. For more information, see /Gy (Enable Function-Level Linking).
So one may conclude that this optimization is useful, but can break some valid C++ programs. And indeed it is not compliant with C++ standard [expr.eq/3.2] saying
Comparing pointers is defined as follows: ...
- Otherwise, if the pointers are both null, both point to the same function, or both represent the same address, they compare equal.
- Otherwise, the pointers compare unequal.
Since they point not on the same function, the pointers must compare unequal.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With