-
Notifications
You must be signed in to change notification settings - Fork 10.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Sema] Substitute parameter packs when deduced from function arguments #79371
Conversation
@llvm/pr-subscribers-clang Author: Gábor Spaits (spaits) ChangesThis pull request would solve #78449 . The following program is well formed: #include <type_traits>
template <typename... T>
struct args_tag
{
using type = std::common_type_t<T...>;
};
template <typename... T>
void bar(args_tag<T...>, std::type_identity_t<T>..., int, std::type_identity_t<T>...) {}
// example
int main() {
bar(args_tag<int, int>{}, 4, 8, 15, 16, 23);
} but Clang rejects it, while GCC and MSVC doesn't. The reason for this is that, in The logic that handles substitution when we have explicit template arguments does not work here, since the types of the pack are not pushed to The solution proposed in this PR works similar to the trailing pack deduction. The main difference here is the end of the deduction cycle. There is another possible approach that would be less efficient. In the loop when we get to an element of Full diff: https://github.com/llvm/llvm-project/pull/79371.diff 1 Files Affected:
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index e9e7ab5bb6698a0..46fa9eece3747a2 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -730,6 +730,7 @@ class PackDeductionScope {
void addPack(unsigned Index) {
// Save the deduced template argument for the parameter pack expanded
// by this pack expansion, then clear out the deduction.
+ DeducedFromEarlierParameter = !Deduced[Index].isNull();
DeducedPack Pack(Index);
Pack.Saved = Deduced[Index];
Deduced[Index] = TemplateArgument();
@@ -858,6 +859,29 @@ class PackDeductionScope {
Info.PendingDeducedPacks[Pack.Index] = Pack.Outer;
}
+ std::optional<unsigned> getSavedPackSize(unsigned Index,
+ TemplateArgument Pattern) const {
+
+ SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+ S.collectUnexpandedParameterPacks(Pattern, Unexpanded);
+ if (Unexpanded.size() == 0 ||
+ Packs[0].Saved.getKind() != clang::TemplateArgument::Pack)
+ return {};
+ unsigned PackSize = Packs[0].Saved.pack_size();
+
+ if (std::all_of(Packs.begin() + 1, Packs.end(),
+ [&PackSize](auto P) {
+ return P.Saved.getKind() == TemplateArgument::Pack &&
+ P.Saved.pack_size() == PackSize;
+ }))
+ return PackSize;
+ return {};
+ }
+
+ /// Determine whether this pack has already been deduced from a previous
+ /// argument.
+ bool isDeducedFromEarlierParameter() const {return DeducedFromEarlierParameter;}
+
/// Determine whether this pack has already been partially expanded into a
/// sequence of (prior) function parameters / template arguments.
bool isPartiallyExpanded() { return IsPartiallyExpanded; }
@@ -970,7 +994,6 @@ class PackDeductionScope {
NewPack = Pack.DeferredDeduction;
Result = checkDeducedTemplateArguments(S.Context, OldPack, NewPack);
}
-
NamedDecl *Param = TemplateParams->getParam(Pack.Index);
if (Result.isNull()) {
Info.Param = makeTemplateParameter(Param);
@@ -1003,9 +1026,12 @@ class PackDeductionScope {
unsigned PackElements = 0;
bool IsPartiallyExpanded = false;
bool DeducePackIfNotAlreadyDeduced = false;
+ bool DeducedFromEarlierParameter = false;
+
/// The number of expansions, if we have a fully-expanded pack in this scope.
std::optional<unsigned> FixedNumExpansions;
+
SmallVector<DeducedPack, 2> Packs;
};
@@ -4371,6 +4397,41 @@ Sema::TemplateDeductionResult Sema::DeduceTemplateArguments(
// corresponding argument is a list?
PackScope.nextPackElement();
}
+ } else if (!IsTrailingPack && !PackScope.isPartiallyExpanded() &&
+ PackScope.isDeducedFromEarlierParameter() &&
+ !isa<PackExpansionType>(ParamTypes[ParamIdx + 1])) {
+ // [temp.deduct.general#3]
+ // When all template arguments have been deduced
+ // or obtained from default template arguments, all uses of template
+ // parameters in the template parameter list of the template are
+ // replaced with the corresponding deduced or default argument values
+ //
+ // If we have a trailing parameter pack, that has been deduced perviously
+ // we substitute the pack here in a similar fashion as seen above with
+ // the trailing parameter packs. The main difference here is that, in
+ // this case we are not processing all of the remaining arguments. We
+ // are only process as many arguments as much we have in the already
+ // deduced parameter.
+ SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+ collectUnexpandedParameterPacks(ParamPattern, Unexpanded);
+ if (Unexpanded.size() == 0)
+ continue;
+
+ std::optional<unsigned> ArgPosAfterSubstitution =
+ PackScope.getSavedPackSize(getDepthAndIndex(Unexpanded[0]).second,
+ ParamPattern);
+ if (!ArgPosAfterSubstitution)
+ continue;
+
+ unsigned PackArgEnd = ArgIdx + *ArgPosAfterSubstitution;
+ for (; ArgIdx < PackArgEnd && ArgIdx < Args.size(); ArgIdx++) {
+ ParamTypesForArgChecking.push_back(ParamPattern);
+ if (auto Result = DeduceCallArgument(ParamPattern, ArgIdx,
+ /*ExplicitObjetArgument=*/false))
+ return Result;
+
+ PackScope.nextPackElement();
+ }
}
}
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
@@ -431,6 +442,17 @@ namespace deduction_after_explicit_pack { | |||
i<int, int>(0, 1, 2, 3, 4, 5); // expected-error {{no match}} | |||
} | |||
|
|||
template <typename... T> | |||
void bar(args_tag<T...>, type_identity_t<T>..., int mid, type_identity_t<T>...) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens without the mid parameter ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could work. It would cost only condition being removed. I deliberately put in the condition that disables this. Should I enable it?
I think enabling it would be standard compliant, but I played it safe and stuck to the example seen in the issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, i think we should support that, and add a test for it )as long as the standard says it should work, which i think it does
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a test for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see a test for
template <typename... Y, typename... T>
void foo2(args_tag<Y...>, args_tag<T...>, type_identity_t<T>..., type_identity_t<T>...) {}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the test. It also works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this!
It looks like a good direction.
I left a few comments
Can you add a changelog entry? Thanks
Thank you @cor3ntin for reviewing my PR. I addressed all of your comments.
Could you please review my recent changes? |
6ead8a4
to
91074e6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the release note, and there is a typo in the PR entry that should be fixed.
Else just 1 nit.
Oh sorry I forgot about the release note. I will update that. |
1307230
to
18befe2
Compare
@cor3ntin @erichkeane |
Looks like the release notes change failed CI. |
Not my changes. This was commited 3 hours ago: spaits@c92ad41 . Should I fix it for this commit? |
e0ce5e0
to
28e9d92
Compare
It looks like libc++26 test suites are failing. Looking at the logs from the CI I can not really figure out what is the exact reason. |
Co-authored-by: Erich Keane <ekeane@nvidia.com>
28e9d92
to
fdf3a00
Compare
I did some more digging. I saw that the test in the CI fails with permission errors.
I thought that maybe the problem is that, the program to be debugged by
Because of the GCC 13+ prereq. I did not build libc++ for my self, but used the one available on my machine. I will still try to run the tests myself. Until that if you have any idea what should I, do what wen possibly wrong pleas share that with me. Thanks. |
Co-authored-by: cor3ntin <corentinjabot@gmail.com>
It could very well just be a transient error/something that failed and has since been fixed. See if it still happens after this pending build. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this!
The changes looks good to me.
Will you need me to merge them on your behalf?
Thank you for reviewing. Before merge we should take a look at the CI. It still fails for libc++ 26 suite, with the same reason as before. I wanted to reproduce the issue. I built libc++ with the clang++ I compiled:
Then I run the test:
Everything worked fine for me. |
@cor3ntin I see that you have CI runs that fail with exactly the same reason as my runs: https://buildkite.com/llvm-project/clang-ci/builds/10874#018d49c2-1224-4939-9430-0e5a2be796a9 . |
It's unrelated to your changes. Configuration issue on the CI, probably |
Okay. Then I will merge this. Thank you very much for reviewing my PR and helping me. |
This pull request would solve #78449 .
There is also a discussion about this on stackoverflow: https://stackoverflow.com/questions/77832658/stdtype-identity-to-support-several-variadic-argument-lists .
The following program is well formed:
but Clang rejects it, while GCC and MSVC doesn't. The reason for this is that, in
Sema::DeduceTemplateArguments
we are not prepared for this case.Substitution/deduction of parameter packs
The logic that handles substitution when we have explicit template arguments (
SubstituteExplicitTemplateArguments
) does not work here, since the types of the pack are not pushed toParamTypes
before the loop starts that does the deduction.The other "candidate" that may could have handle this case would be the loop that does the deduction for trailing packs, but we are not dealing with trailing packs here.
Solution proposed in this PR
The solution proposed in this PR works similar to the trailing pack deduction. The main difference here is the end of the deduction cycle.
When a non-trailing template pack argument is found, whose type is not explicitly specified and the next type is not a pack type, the length of the previously deduced pack is retrieved (let that length be
s
). After that the nexts
arguments are processed in the same way as in the case of non trailing packs.Another possible solution
There is another possible approach that would be less efficient. In the loop when we get to an element of
ParamTypes
that is a pack and could be substituted because the type is deduced from a previous argument, thens
number of arg types would be inserted before the current element ofParamTypes
type. Then we would "cancel" the processing of the current element, first process the previously inserted elements and the after that re-process the current element.Basically we would do what
SubstituteExplicitTemplateArguments
does but during deduction.Adjusted test cases
In
clang/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.call/p1-0x.cpp
there is a test case namedtest_pack_not_at_end
that should work, but still does not. This test case is relevant because the note for the error message has changed.This is what the test case looks like currently:
The previous note said (before my changes):
The current note says (after my changesand also clang 14 would say this if the pack was not trailing):
GCC says: