This is an archive of the discontinued LLVM Phabricator instance.

[c++] implements tentative DR1432 for partial ordering of function template
ClosedPublic

Authored by ychen on Sep 12 2022, 12:36 AM.

Details

Summary

D128745 handled DR1432 for the partial ordering of partial specializations, but
missed the handling for the partial ordering of function templates. This patch
implements the latter. While at it, also simplifies the previous implementation to
be more close to the wording without functional changes.

Fixes https://github.com/llvm/llvm-project/issues/56090

Diff Detail

Event Timeline

ychen created this revision.Sep 12 2022, 12:36 AM
Herald added a project: Restricted Project. · View Herald TranscriptSep 12 2022, 12:36 AM
ychen requested review of this revision.Sep 12 2022, 12:36 AM
Herald added a project: Restricted Project. · View Herald TranscriptSep 12 2022, 12:36 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript
ychen added a reviewer: Restricted Project.Sep 12 2022, 12:36 AM
ychen edited the summary of this revision. (Show Details)
mizvekov added inline comments.
clang/lib/Sema/SemaTemplateDeduction.cpp
5230–5231

This should work, as all the properties you are testing below are present on the canonical type.

5497–5515

This is a bug, T1 and T2 are not in general canonical types for this function, and this castAs can pick up an alias template, and these should never participate in deduction.

(Also, can you please add a test for that?)

You should not need to keep sugar here, everything being tested below is present on the canonical type, so this is simple to solve, as suggested.

ychen added a comment.Sep 13 2022, 5:27 PM

@mizvekov Thanks for taking a look.

clang/lib/Sema/SemaTemplateDeduction.cpp
5230–5231

Gotta admit that I was confused by DesugaredType and CanonicalType. But since you pointed it out, I could see the differences now. The CanonicalType could be sugared, and desugaring is not necessary here. Thanks.

5497–5515

I see. I'll add a test.

ychen updated this revision to Diff 461051.Sep 18 2022, 12:49 AM
  • address comments
  • rebase
ychen added inline comments.Sep 18 2022, 1:01 AM
clang/lib/Sema/SemaTemplateDeduction.cpp
5497–5515

Looking more closely, T1/T2 is mostly returned by ASTContext::getTemplateSpecializationType and for injected template specialization, always unqualified https://github.com/llvm/llvm-project/blob/52dce8900c46d5842a021619537ede598983dfde/clang/include/clang/AST/Type.h#L5431-L5432 , it does not seem possible to have aliase template involved? I could've missed something though.

mizvekov added inline comments.Sep 18 2022, 8:01 AM
clang/lib/Sema/SemaTemplateDeduction.cpp
5497–5515

Yeah you are right, I missed that all the uses of this function, those types either come from an Injected TST, or a dependent TST created on the spot.

But yeah, the change to a simple cast instead of castAs makes this more clear.

Otherwise, if there could have been any sugar node on top of the TST, that sugar could have easily been an alias TST,
and I think it's very unlikely one wouldn't care, as they are very different things.

So I tend to view these as potential bugs, a simple getAs and castAs on TSTs, which does not check or keeps digging through alias templates is highly suspicious :)

Thanks for taking a look at this though!

mizvekov added inline comments.Sep 18 2022, 9:29 AM
clang/lib/Sema/SemaTemplateDeduction.cpp
5205–5258

One thing I have been thinking about all of these "more constrained" checks, is whether we should actually be performing them over the results of the deductions for the least constrained checks.

Ie especially with regards to this new check and the one for [temp.deduct.partial]p11:, it seems that we should be instead considering as more constrained the deduction which deduced a lesser amount of template parameters, including ones in packs. Or something along this line.

This way, we do one sweeping general rule to handle all these cases, instead of just appending these ad-hoc tie-breaking rules which look more like workarounds.

What do you think about that?

But this is a big change and I don't want to block you on that. Because otherwise, this new change is keeping in line with other workarounds that have already been added here, so this is within existing practice.

Since this patch was submitted quite recently and it's a speculative fix, I would let this patch marinate a little more and wait for any other reviewers.

5225

I would make it more clear in this comment that this is a speculative fix, that there is no wording or even resolution for this issue.

clang/test/CXX/drs/dr6xx.cpp
1104

I would expect the call to g not to be ambiguous here, in the same vein of keeping with the spirit of the rules.

If you agree, can we at least add a FIXME to make a note of that?

ychen added inline comments.Sep 18 2022, 9:32 AM
clang/lib/Sema/SemaTemplateDeduction.cpp
5497–5515

Thanks for the explanation. Yeah, castAs looks misleading indeed. I wasn't aware of the subtle difference between cast and castAs. BTW, are you comfortable accepting this patch?

mizvekov added inline comments.Sep 18 2022, 9:40 AM
clang/lib/Sema/SemaTemplateDeduction.cpp
5497–5515

I hope I answered that in my other comment, but since this is a speculative wording fix, I think it's more reasonable to wait more than a week for other reviewers, specially since folks can be on time off and such.

But lacking interest from other folks in reviewing this, yeah sure.

ychen added inline comments.Sep 18 2022, 9:53 AM
clang/lib/Sema/SemaTemplateDeduction.cpp
5205–5258

Ie especially with regards to this new check and the one for [temp.deduct.partial]p11:, it seems that we should be instead considering as more constrained the deduction which deduced a lesser amount of template parameters, including ones in packs. Or something along this line.

This way, we do one sweeping general rule to handle all these cases, instead of just appending these ad-hoc tie-breaking rules which look more like workarounds.

What do you think about that?

I think you meant the cases that for a P/A deduction pair, either P/A is pack and the other is not empty, right? If so, it already works that way. https://eel.is/c++draft/temp.deduct#type-9

If neither P/A is pack and either is empty, then they wouldn't get into the stage of partial ordering at all because the function arguments list can only have one length, P/A would not be in the same candidate sets.

Here handles the case that either P/A is pack and the other is empty. Then the deduction rule could not kick in because one of the P/Apair is none. So we have to tie-break after the deduction.

mizvekov added inline comments.Sep 18 2022, 10:06 AM
clang/lib/Sema/SemaTemplateDeduction.cpp
5205–5258

I understand we need to tie break after the at-least-as-specialized check, but I was wondering if we could come up with one sweeping rule / mechanism, which incorporates not only these two cases but also the concept more-constrained check, instead of appending multiple completely separate tie breakers.

Otherwise, I think this gets pretty hard to incorporate into your mental model of what overload resolution should pick up and such.

For example, even the order which we tie break seems arbitrary, and this makes it harder for folks to learn / memorize them and for them to actually be useful, instead of just programming traps. But this is C++ so I may be barking at the wrong tree.

ychen added inline comments.
clang/lib/Sema/SemaTemplateDeduction.cpp
5205–5258

The rules are complicated and we should definitely simplify these if possible. I'll give it more thought. @hubert.reinterpretcast worked a lot in this area. If he is interested, may shed some light here.

5497–5515

Thanks, that makes sense. I wasn't aware of your other comments when writing this.

clang/test/CXX/drs/dr6xx.cpp
1104

Yeah, I agree it is intuitive to work that way. I think it is not in the standard right now because it does not have a strong use case and it requires a big change because right now deduction for partial ordering and the following tie-breakers do not consider the function template parameter list at all, if we do this, then all partial orderings and template specializations are affected.

The use case of CWG1432 is partial specialization which is very important and easier to catch attention.

ychen updated this revision to Diff 463019.Sep 26 2022, 2:22 PM
ychen marked an inline comment as done.Sep 26 2022, 2:22 PM

ping ?

mizvekov accepted this revision.Sep 27 2022, 2:34 AM

Looks good, aside some points:

  • Please add test for the conditioning on ClangABICompat15.
  • Missing release notes.
  • FWIW GCC treats the g(42) case as not ambiguous, as I expected, so it might be worth pointing that out with a FIXME regardless.
This revision is now accepted and ready to land.Sep 27 2022, 2:34 AM
erichkeane accepted this revision.Sep 27 2022, 5:54 AM

Just fix @mizvekov 's nits (the fixme, release notes, and ABI test), and LGTM.

ychen updated this revision to Diff 463283.Sep 27 2022, 11:03 AM
  • add test for the conditioning on ClangABICompat15.
  • Missing release notes.

I didn't add that because this patch is logically part of D128745 which has a release note already.

  • FWIW GCC treats the g(42) case as not ambiguous, as I expected, so it might be worth pointing that out with a FIXME regardless.

I think GCC is the one that is not conforming. MSVC agrees it is ambiguous (https://godbolt.org/z/T5zbxaTYq). Basically, only the first parameter is considered for partial ordering. (https://eel.is/c++draft/temp.deduct.partial#3.1)

I didn't add that because this patch is logically part of D128745 which has a release note already.

Okay, I see that the existing release note entry talks just about implementing those DRs without going specific, so that is fine.
It does not mention that DR1432 implementation is tentative as there is no published resolution though.

I think GCC is the one that is not conforming. MSVC agrees it is ambiguous (https://godbolt.org/z/T5zbxaTYq). Basically, only the first parameter is considered for partial ordering. (https://eel.is/c++draft/temp.deduct.partial#3.1)

I could totally buy that the GCC behavior is accidental, but it does make sense, with the idea behind DR1432, that we should prefer the first overload because that deduction is simpler, or because it does not deduce a parameter pack.

If there was a resolution, the answer here would be clear cut, but since there isn't, we are making up our own rules and getting into a language design discussion.
If you don't think that is clear cut, it's fine, we can move this discussion elsewhere :)

I didn't add that because this patch is logically part of D128745 which has a release note already.

Okay, I see that the existing release note entry talks just about implementing those DRs without going specific, so that is fine.
It does not mention that DR1432 implementation is tentative as there is no published resolution though.

I think GCC is the one that is not conforming. MSVC agrees it is ambiguous (https://godbolt.org/z/T5zbxaTYq). Basically, only the first parameter is considered for partial ordering. (https://eel.is/c++draft/temp.deduct.partial#3.1)

I could totally buy that the GCC behavior is accidental, but it does make sense, with the idea behind DR1432, that we should prefer the first overload because that deduction is simpler, or because it does not deduce a parameter pack.

If there was a resolution, the answer here would be clear cut, but since there isn't, we are making up our own rules and getting into a language design discussion.
If you don't think that is clear cut, it's fine, we can move this discussion elsewhere :)

It is clear cut by https://eel.is/c++draft/temp.deduct.partial#3.1. It is orthogonal to the DRs implemented in D128745 and here.
https://godbolt.org/z/s49sqrabY : if you call g(42,42), it would reveal that the function without pack indeed wins. It is ambiguous because the caller g(42) does not use the pack. So the wording prevents us from letting the non-pack version win. I do somewhat agree that it is more consistent and predictable to always consider all function parameters for partial ordering. Yeah, that needs a whole separate discussion on https://eel.is/c++draft/temp.deduct.partial#3.1.

ychen updated this revision to Diff 463309.Sep 27 2022, 12:07 PM
  • Update release notes

It is clear cut by https://eel.is/c++draft/temp.deduct.partial#3.1. It is orthogonal to the DRs implemented in D128745 and here.
https://godbolt.org/z/s49sqrabY : if you call g(42,42), it would reveal that the function without pack indeed wins. It is ambiguous because the caller g(42) does not use the pack. So the wording prevents us from letting the non-pack version win. I do somewhat agree that it is more consistent and predictable to always consider all function parameters for partial ordering. Yeah, that needs a whole separate discussion on https://eel.is/c++draft/temp.deduct.partial#3.1.

I think it's using the pack, the pack is just being deduced as an empty pack. This is different from a template parameter which is not being deduced because it does not appear in the arguments.

To reiterate, what you said would make sense if the second overload was written as:

template <typename T, typename... U> void g(T);

Instead of template <typename T, typename... U> void g(T, U...); as it is.

It is clear cut by https://eel.is/c++draft/temp.deduct.partial#3.1. It is orthogonal to the DRs implemented in D128745 and here.
https://godbolt.org/z/s49sqrabY : if you call g(42,42), it would reveal that the function without pack indeed wins. It is ambiguous because the caller g(42) does not use the pack. So the wording prevents us from letting the non-pack version win. I do somewhat agree that it is more consistent and predictable to always consider all function parameters for partial ordering. Yeah, that needs a whole separate discussion on https://eel.is/c++draft/temp.deduct.partial#3.1.

I think it's using the pack, the pack is just being deduced as an empty pack. This is different from a template parameter which is not being deduced because it does not appear in the arguments.

To reiterate, what you said would make sense if the second overload was written as:

template <typename T, typename... U> void g(T);

Instead of template <typename T, typename... U> void g(T, U...); as it is.

This is described in https://eel.is/c++draft/temp.deduct.partial#13. Parietal ordering currently doesn't consider the number of deduced arguments. During the partial ordering stage, by https://eel.is/c++draft/temp.func.order#3, template <typename T, typename... U> void g(T, U...); would become void g(UniqueT1, UniqueT2);. And then it applies https://eel.is/c++draft/temp.deduct.partial#3.1 to decide which parameters are compared for partial ordering.

This is described in https://eel.is/c++draft/temp.deduct.partial#13. Parietal ordering currently doesn't consider the number of deduced arguments. During the partial ordering stage, by https://eel.is/c++draft/temp.func.order#3, template <typename T, typename... U> void g(T, U...); would become void g(UniqueT1, UniqueT2);. And then it applies https://eel.is/c++draft/temp.deduct.partial#3.1 to decide which parameters are compared for partial ordering.

Sure, one thing is, given that we deduced a pack, does the number of elements deduced for that pack matter in partial ordering. I think temp.deduct.partial#13 answers that it doesn't.

The other thing is, is a given template parameter pack used, or deduced, as opposed to just not being used. and I think the answer here is yes for that second overload of g.

If a template parameter pack appears as a type of a function parameter in an expansion pattern, such as void g(T, U...);, then I think that, notionally, there is no way to not use it.

For any call to that overload, that parameter pack will be deduced, no matter if to an empty pack or otherwise.

ychen added a comment.Sep 27 2022, 1:12 PM

This is described in https://eel.is/c++draft/temp.deduct.partial#13. Parietal ordering currently doesn't consider the number of deduced arguments. During the partial ordering stage, by https://eel.is/c++draft/temp.func.order#3, template <typename T, typename... U> void g(T, U...); would become void g(UniqueT1, UniqueT2);. And then it applies https://eel.is/c++draft/temp.deduct.partial#3.1 to decide which parameters are compared for partial ordering.

Sure, one thing is, given that we deduced a pack, does the number of elements deduced for that pack matter in partial ordering. I think temp.deduct.partial#13 answers that it doesn't.

The other thing is, is a given template parameter pack used, or deduced, as opposed to just not being used. and I think the answer here is yes for that second overload of g.

If a template parameter pack appears as a type of a function parameter in an expansion pattern, such as void g(T, U...);, then I think that, notionally, there is no way to not use it.

That's right. That requires the wording says something like pack is always used for partial ordering. I just realized that this g(42) example is from https://eel.is/c++draft/temp.func.order#note-3.