Date: Wed, 5 Feb 2025 07:37:13 +0000
>> From my understanding you can actually do quite a lot without much magic.
>>
>> I have found 2 categories of problems.
>> 1. Non-aliased types. Where the forwarded type is the actual name of
>> the type. It will be one of class, struct, or union, without needing
>> to specify it as a class, struct, or union.
>>
>> 1 is super easy, it looks like pretty much everything works, template,
>> constexpr, function calls, etc... I haven't noticed a problem yet.
>> Everything that you can do with a trivially forward declared type you
>> would be able to do with this. The only difference is you don’t have
>> to specify class, struct, or union, it’s one of those 3.
> Yeah, no, that's not going to work. The compiler must know what it is. Unions are not structs/classes, so the mangling is allowed to be different, and it is for MSVC. MSVC also mangles structs differently from classes, in violation of the C++ standard, but we shouldn't design a feature that is DOA for MSVC.
> GCC & Clang also support an ABI-tagging of types (__attribute__((abi))) which will influence the mangling, so those need to be provided too. This would allow MSVC to also include another of its standards-violating requirements: the representation format for PMFs formed from this class (see https://learn.microsoft.com/en-us/cpp/cpp/inheritance-keywords?view=msvc-170).
> And it would allow other attributes that the implementation might decide should be queryable without the full definition (like __declspec(uuid) for __uuidof).
> Therefore, the solution for resolving needs to be a mechanism that inform the compiler of all of that.
It may make things harder, but I don't see it as a deal breaker.
Do you really need to know the mangled name at compile time?
Couldn't the compiler just use the fully declared name, mark it as an alias, and when it actually needs to know the name (which is at most when exporting symbols from a library), and then as long as it is marked somewhere when linking the linker can go "oh yeah, I know what this type is, this is how I'm going to name it"?
> I think that's overengineering and dramatically reduces the chance of adoption. If we require binary format and toolchain updates, the barrier of adoption goes up. It will have ripple effects many years later, as we learn all of the effects (intentional or not) of having them.
I wouldn't say its overengineering, it's a difficult problem to address that requires extra work to achieve.
It's a matter of does the reward offset the cost?
Compilers had to be remade to accommodate things like constexpr, and I think we are now better off for it.
I'm an advocate for extreme simplicity where possible, but simplicity at all costs, the problem should have the exact amount of complexity as needed and I think this is the minimum level.
Take for example:
Nested forward declaration
struct A;
struct A::B;
void* func(A::B&);
One thing that I think is important when compiling code is consistency.
This piece of snippet shouldn't all of a sudden stop working when the actual definition is provided.
But what if "struct A::B" is private? Or even non-existent?
Lacking this information the code must compile, must generate a symbol, but if the actual definition of A is provided then it wouldn't. The program is ill formed.
It's a bit of a bummer, but's lets analyze these 2 scenarios.
1. The easy one "struct A::B" doesn't even exist.
Well, the following is currently allowed:
struct A; //never actually defined
void* func(A&);
A symbol might be generated, but it is difficult to properly use it anyway without actually there being an A defined somewhere, where you can get an A object.
We can say that "void* func(A::B&);" is equally ill formed (except you can say for sure that it is ill formed) and some compilation unit that may try to get a A::B, will certainly find itself in a awkward position of not being able to defined the object and thus fail to compile.
2. Now let's consider that "struct A::B" exists but it is not public to "void* func(A::B&);"
Again, the application must compile, symbols must be generated.
You would think, when someone would try to use it the compiler would need to see the definition and catch this problem, but actually no.
While the type can be private to "void* func(A::B&);" it can be public to something else that uses "func", at which point it may no longer have that information.
Thus, leading to a scenario where struct A::B is private, but you have function that can reference the type, and the application compiles. Sure, you might not be able to do more than "take address", maybe that's ok. But either or not you get actually generated code can change depending on either or not both things are visible in the same translation unit.
Unless.... you hoist the access information to link time. In that case you would still get an error, just at a different stage.
Linker work might be necessary.
>>
>> I have found 2 categories of problems.
>> 1. Non-aliased types. Where the forwarded type is the actual name of
>> the type. It will be one of class, struct, or union, without needing
>> to specify it as a class, struct, or union.
>>
>> 1 is super easy, it looks like pretty much everything works, template,
>> constexpr, function calls, etc... I haven't noticed a problem yet.
>> Everything that you can do with a trivially forward declared type you
>> would be able to do with this. The only difference is you don’t have
>> to specify class, struct, or union, it’s one of those 3.
> Yeah, no, that's not going to work. The compiler must know what it is. Unions are not structs/classes, so the mangling is allowed to be different, and it is for MSVC. MSVC also mangles structs differently from classes, in violation of the C++ standard, but we shouldn't design a feature that is DOA for MSVC.
> GCC & Clang also support an ABI-tagging of types (__attribute__((abi))) which will influence the mangling, so those need to be provided too. This would allow MSVC to also include another of its standards-violating requirements: the representation format for PMFs formed from this class (see https://learn.microsoft.com/en-us/cpp/cpp/inheritance-keywords?view=msvc-170).
> And it would allow other attributes that the implementation might decide should be queryable without the full definition (like __declspec(uuid) for __uuidof).
> Therefore, the solution for resolving needs to be a mechanism that inform the compiler of all of that.
It may make things harder, but I don't see it as a deal breaker.
Do you really need to know the mangled name at compile time?
Couldn't the compiler just use the fully declared name, mark it as an alias, and when it actually needs to know the name (which is at most when exporting symbols from a library), and then as long as it is marked somewhere when linking the linker can go "oh yeah, I know what this type is, this is how I'm going to name it"?
> I think that's overengineering and dramatically reduces the chance of adoption. If we require binary format and toolchain updates, the barrier of adoption goes up. It will have ripple effects many years later, as we learn all of the effects (intentional or not) of having them.
I wouldn't say its overengineering, it's a difficult problem to address that requires extra work to achieve.
It's a matter of does the reward offset the cost?
Compilers had to be remade to accommodate things like constexpr, and I think we are now better off for it.
I'm an advocate for extreme simplicity where possible, but simplicity at all costs, the problem should have the exact amount of complexity as needed and I think this is the minimum level.
Take for example:
Nested forward declaration
struct A;
struct A::B;
void* func(A::B&);
One thing that I think is important when compiling code is consistency.
This piece of snippet shouldn't all of a sudden stop working when the actual definition is provided.
But what if "struct A::B" is private? Or even non-existent?
Lacking this information the code must compile, must generate a symbol, but if the actual definition of A is provided then it wouldn't. The program is ill formed.
It's a bit of a bummer, but's lets analyze these 2 scenarios.
1. The easy one "struct A::B" doesn't even exist.
Well, the following is currently allowed:
struct A; //never actually defined
void* func(A&);
A symbol might be generated, but it is difficult to properly use it anyway without actually there being an A defined somewhere, where you can get an A object.
We can say that "void* func(A::B&);" is equally ill formed (except you can say for sure that it is ill formed) and some compilation unit that may try to get a A::B, will certainly find itself in a awkward position of not being able to defined the object and thus fail to compile.
2. Now let's consider that "struct A::B" exists but it is not public to "void* func(A::B&);"
Again, the application must compile, symbols must be generated.
You would think, when someone would try to use it the compiler would need to see the definition and catch this problem, but actually no.
While the type can be private to "void* func(A::B&);" it can be public to something else that uses "func", at which point it may no longer have that information.
Thus, leading to a scenario where struct A::B is private, but you have function that can reference the type, and the application compiles. Sure, you might not be able to do more than "take address", maybe that's ok. But either or not you get actually generated code can change depending on either or not both things are visible in the same translation unit.
Unless.... you hoist the access information to link time. In that case you would still get an error, just at a different stage.
Linker work might be necessary.
Received on 2025-02-05 07:37:22