Skip to content

[Sema] Substitute parameter packs when deduced from function arguments #79371

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

Merged
merged 16 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ C++2c Feature Support

Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Substitute template parameter pack, when it is not explicitly specified
in the template parameters, but is deduced from a previous argument.
(`#78449: <https://github.com/llvm/llvm-project/issues/78449>`_).

C Language Changes
------------------
Expand Down
51 changes: 51 additions & 0 deletions clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -858,6 +859,27 @@ class PackDeductionScope {
Info.PendingDeducedPacks[Pack.Index] = Pack.Outer;
}

// Return the size of the saved packs if all of them has the same size.
std::optional<unsigned> getSavedPackSizeIfAllEqual() const {
if (Packs.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](const 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; }
Expand Down Expand Up @@ -1003,6 +1025,7 @@ 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;

Expand Down Expand Up @@ -4371,6 +4394,34 @@ Sema::TemplateDeductionResult Sema::DeduceTemplateArguments(
// corresponding argument is a list?
PackScope.nextPackElement();
}
} else if (!IsTrailingPack && !PackScope.isPartiallyExpanded() &&
PackScope.isDeducedFromEarlierParameter()) {
// [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
// previously we substitute the pack here in a similar fashion as
// 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.
std::optional<unsigned> ArgPosAfterSubstitution =
PackScope.getSavedPackSizeIfAllEqual();
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();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ void test_pair_deduction(int *ip, float *fp, double *dp) {
template<typename ...Types> struct tuple { };

template<typename ...Types>
void pack_not_at_end(tuple<Types...>, Types... values, int); // expected-note {{<int *, double *> vs. <>}}
void pack_not_at_end(tuple<Types...>, Types... values, int); // expected-note {{<int *, double *> vs. <int, int>}}

void test_pack_not_at_end(tuple<int*, double*> t2) {
pack_not_at_end(t2, 0, 0, 0); // expected-error {{no match}}
Expand Down
28 changes: 28 additions & 0 deletions clang/test/SemaTemplate/deduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ struct X0<int, A> {
static const unsigned value = 1;
};

template<class T>
struct type_identity {
using type = T;
};

template<class T>
using type_identity_t = typename type_identity<T>::type;

template <typename... T>
struct args_tag {};

template<int> struct X0i;
template<long> struct X0l;
int array_x0a[X0<long, X0l>::value == 0? 1 : -1];
Expand Down Expand Up @@ -431,6 +442,23 @@ 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>...) {}
Copy link
Contributor

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 ?

Copy link
Contributor Author

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.

Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

@cor3ntin cor3ntin Jan 26, 2024

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>...) {}

Copy link
Contributor Author

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.

void call_bar() {
bar(args_tag<int, int>{}, 4, 8, 1001, 16, 23);
}

template <typename... Y, typename... T>
void foo(args_tag<Y...>, args_tag<T...>, type_identity_t<T>..., int mid, type_identity_t<T>...) {}
void call_foo() {
foo(args_tag<const int,const int, const int>{}, args_tag<int, int, int>{}, 4, 8, 9, 15, 16, 23, 1);
}

template <typename... Y, typename... T> void baz(args_tag<T...>, T..., T...) {}
void call_baz() {
baz(args_tag<int, int>{}, 1, 2, 3, 4);
}

// GCC alarmingly accepts this by deducing T={int} by matching the second
// parameter against the first argument, then passing the first argument
// through the first parameter.
Expand Down