Date: Mon, 28 Apr 2025 12:56:29 -0400
On Sun, Apr 27, 2025 at 1:22 PM Brian Bi via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> On Sun, Apr 27, 2025 at 12:42 PM Tiago Freire via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> > Why do you want different things to pretend they're the same thing? If
>> you want the language to be changed, you need to offer a reason why it
>> would be good, not simply "why not".
>>
>> My proposition was that because it could produce smaller binaries (and
>> allow for better code optimization) is itself reason enough to make the
>> change.
>> And I'm trying to challenge this idea that "functions have different
>> names and therefore should have different addresses, just because".
>> Same logic applies as to why zero size objects shouldn't have at least
>> 1byte. no_unique_address should be the default and not the opt-in exception
>> IMO.
>> [...]
>> A function is not an object, it is a function.
>> There's no reason to reason about functions as if they were objects.
>>
>
> In
> https://github.com/bloomberg/bde/blob/main/groups/bsl/bsltf/bsltf_templatetestfacility.h#L1285
> we use the fact that different functions have different addresses to
> produce 128 unique values of type `TemplateTestFacility::MethodPtr` (which
> is a typedef to a pointer to member function), which are then used to test
> e.g. containers that must work with various kinds of element types.
>
That's not *quite* right [although see below]. You don't rely on the fact
that different functions have different addresses; you rely on the fact
that functions with different *behavior* must *necessarily* have different
addresses.
You use (here
<https://github.com/bloomberg/bde/blob/main/groups/bsl/bsltf/bsltf_templatetestfacility.h#L1089-L1093>
):
template <int IDENTIFIER>
int TemplateTestFacility_StubClass::method() {
return IDENTIFIER;
}
static_assert(&method<1> != &method<2>);
I assume Tiago doesn't propose to break *that*. He would simply standardize
the somewhat-existing practice that we could reasonably put *identical*
functions at identical addresses. (E.g. I know Green Hills' linker has done
this for >20 years. But I don't know how Green Hills solves the problem
below: whether it turns off the optimization if the address is ever taken,
or just lets the behavior be unpredictable at runtime, or what.)
template <int IDENTIFIER>
int TemplateTestFacility_StubClass::method() {
return 0;
}
assert_possibly_true(&method<1> == &method<2>);
Although notice that this is tricky for two reasons. One, the optimizer may
cause functions that "look" different to actually have the same behavior
after optimization. A classic example is:
template<class T> int distance(T *a, T *b) { return b - a; }
static_assert(&distance<int> != &distance<short>); // must have
different codegen, since sizeof(int) != sizeof(short)
assert_possibly_true(&distance<int> == &distance<float>); // can have
identical codegen, since sizeof(int) == sizeof(float)
Two, letting the value of (fptr1 == fptr2) be unspecified at compile time
means that expressions formerly compile-time-constant are no longer
compile-time-constant. For example, today this is legal C++:
int f();
int g();
constexpr bool b = (f == g);
static_assert(b == false);
Making it legal for (f == g) at runtime [that is, "after link-time"] would
make the compiler unable to compile this program. We'd have to bite the
bullet and add programs like this to Annex C — "used to be valid C++, but
no longer." This is totally achievable — after all, this is the very reason
why we have an Annex C — but it certainly puts an item in the "Cons" column
of our "Pros and Cons" table.
In fact, I guess this brings me full circle back to Brian Bi's point.
Consider:
int f();
int g();
template<auto*> struct A {};
A<&f> a;
A<&g> b;
static_assert(!__is_same(decltype(a), decltype(b)));
If `&f == &g`, then this breaks down: Surely `A<value>` must be the same
type whenever `value` is the same value! And if `&f` and `&g` have the same
value, then `A<&f>` and `A<&g>` must be the same type. But we won't know
whether they're the same type until link-time. So... we can't know at
compile-time whether these two types are the same or different? That's...
not something we can physically represent in C++.
So, to make this idea work at all, we would have to retroactively make it
invalid to use *the address of a function* as a template argument — in the
same way it's currently invalid to use the address of a string literal.
But that would almost certainly break someone's code. (Not Brian's example,
AFAICT, since he never uses the unique function address as a template
argument. But surely it'll break *someone's* code.)
HTH,
Arthur
std-proposals_at_[hidden]> wrote:
> On Sun, Apr 27, 2025 at 12:42 PM Tiago Freire via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> > Why do you want different things to pretend they're the same thing? If
>> you want the language to be changed, you need to offer a reason why it
>> would be good, not simply "why not".
>>
>> My proposition was that because it could produce smaller binaries (and
>> allow for better code optimization) is itself reason enough to make the
>> change.
>> And I'm trying to challenge this idea that "functions have different
>> names and therefore should have different addresses, just because".
>> Same logic applies as to why zero size objects shouldn't have at least
>> 1byte. no_unique_address should be the default and not the opt-in exception
>> IMO.
>> [...]
>> A function is not an object, it is a function.
>> There's no reason to reason about functions as if they were objects.
>>
>
> In
> https://github.com/bloomberg/bde/blob/main/groups/bsl/bsltf/bsltf_templatetestfacility.h#L1285
> we use the fact that different functions have different addresses to
> produce 128 unique values of type `TemplateTestFacility::MethodPtr` (which
> is a typedef to a pointer to member function), which are then used to test
> e.g. containers that must work with various kinds of element types.
>
That's not *quite* right [although see below]. You don't rely on the fact
that different functions have different addresses; you rely on the fact
that functions with different *behavior* must *necessarily* have different
addresses.
You use (here
<https://github.com/bloomberg/bde/blob/main/groups/bsl/bsltf/bsltf_templatetestfacility.h#L1089-L1093>
):
template <int IDENTIFIER>
int TemplateTestFacility_StubClass::method() {
return IDENTIFIER;
}
static_assert(&method<1> != &method<2>);
I assume Tiago doesn't propose to break *that*. He would simply standardize
the somewhat-existing practice that we could reasonably put *identical*
functions at identical addresses. (E.g. I know Green Hills' linker has done
this for >20 years. But I don't know how Green Hills solves the problem
below: whether it turns off the optimization if the address is ever taken,
or just lets the behavior be unpredictable at runtime, or what.)
template <int IDENTIFIER>
int TemplateTestFacility_StubClass::method() {
return 0;
}
assert_possibly_true(&method<1> == &method<2>);
Although notice that this is tricky for two reasons. One, the optimizer may
cause functions that "look" different to actually have the same behavior
after optimization. A classic example is:
template<class T> int distance(T *a, T *b) { return b - a; }
static_assert(&distance<int> != &distance<short>); // must have
different codegen, since sizeof(int) != sizeof(short)
assert_possibly_true(&distance<int> == &distance<float>); // can have
identical codegen, since sizeof(int) == sizeof(float)
Two, letting the value of (fptr1 == fptr2) be unspecified at compile time
means that expressions formerly compile-time-constant are no longer
compile-time-constant. For example, today this is legal C++:
int f();
int g();
constexpr bool b = (f == g);
static_assert(b == false);
Making it legal for (f == g) at runtime [that is, "after link-time"] would
make the compiler unable to compile this program. We'd have to bite the
bullet and add programs like this to Annex C — "used to be valid C++, but
no longer." This is totally achievable — after all, this is the very reason
why we have an Annex C — but it certainly puts an item in the "Cons" column
of our "Pros and Cons" table.
In fact, I guess this brings me full circle back to Brian Bi's point.
Consider:
int f();
int g();
template<auto*> struct A {};
A<&f> a;
A<&g> b;
static_assert(!__is_same(decltype(a), decltype(b)));
If `&f == &g`, then this breaks down: Surely `A<value>` must be the same
type whenever `value` is the same value! And if `&f` and `&g` have the same
value, then `A<&f>` and `A<&g>` must be the same type. But we won't know
whether they're the same type until link-time. So... we can't know at
compile-time whether these two types are the same or different? That's...
not something we can physically represent in C++.
So, to make this idea work at all, we would have to retroactively make it
invalid to use *the address of a function* as a template argument — in the
same way it's currently invalid to use the address of a string literal.
But that would almost certainly break someone's code. (Not Brian's example,
AFAICT, since he never uses the unique function address as a template
argument. But surely it'll break *someone's* code.)
HTH,
Arthur
Received on 2025-04-28 16:56:46