Skip to content
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

Clang rejects valid program involving parameter packs with in between type #78449

Closed
ranaanoop opened this issue Jan 17, 2024 · 4 comments
Closed
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" rejects-valid

Comments

@ranaanoop
Copy link

The following program is rejected by clang while both gcc and msvc accepts this. Demo

#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);
   
}

Clang says:

<source>:15:5: error: no matching function for call to 'bar'
   15 |     bar(args_tag<int, int>{}, 4, 8, 15, 16, 23);
      |     ^~~
<source>:10:6: note: candidate template ignored: deduced packs of different lengths for parameter 'T' (<int, int> vs. <>)
   10 | void bar(args_tag<T...>, std::type_identity_t<T>..., int, std::type_identity_t<T>...) {}
      |      ^
@github-actions github-actions bot added the clang Clang issues not falling into any other category label Jan 17, 2024
@EugeneZelenko EugeneZelenko added clang:frontend Language frontend issues, e.g. anything involving "Sema" and removed clang Clang issues not falling into any other category labels Jan 17, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Jan 17, 2024

@llvm/issue-subscribers-clang-frontend

Author: Anoop Rana (ranaanoop)

The following program is rejected by clang while both gcc and msvc accepts this. [Demo](https://godbolt.org/z/rjxP9bM5P)
#include &lt;type_traits&gt;

template &lt;typename... T&gt;
struct args_tag
{
    using type = std::common_type_t&lt;T...&gt;;
};

template &lt;typename... T&gt;
void bar(args_tag&lt;T...&gt;, std::type_identity_t&lt;T&gt;..., int, std::type_identity_t&lt;T&gt;...) {}


// example
int main() {
    bar(args_tag&lt;int, int&gt;{}, 4, 8, 15, 16, 23);
   
}

Clang says:

&lt;source&gt;:15:5: error: no matching function for call to 'bar'
   15 |     bar(args_tag&lt;int, int&gt;{}, 4, 8, 15, 16, 23);
      |     ^~~
&lt;source&gt;:10:6: note: candidate template ignored: deduced packs of different lengths for parameter 'T' (&lt;int, int&gt; vs. &lt;&gt;)
   10 | void bar(args_tag&lt;T...&gt;, std::type_identity_t&lt;T&gt;..., int, std::type_identity_t&lt;T&gt;...) {}
      |      ^

@HolyBlackCat
Copy link

HolyBlackCat commented Jan 18, 2024

Another related example:

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

template <typename ...T>
void bar(args_tag<T...>, T..., int) {}

// example
int main() {
    // note: candidate template ignored: deduced packs of different lengths for parameter 'T' (<int, int> vs. <>)
    bar(args_tag<int, int>{}, 4, 8, 15);
}

Here typename ...T shouldn't be deducible from the function parameter pack, because it's not the last parameter, yet Clang deduces the pack as empty from that parameter.

@ranaanoop
Copy link
Author

spaits added a commit that referenced this issue Jan 27, 2024
#79371)

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:
```cpp
#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 `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 to `ParamTypes` 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 next `s` 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,
then `s` number of arg types would be inserted before the current
element of `ParamTypes` 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 named `test_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:
```cpp
template<typename ...Types>
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}}
  // FIXME: Should the "original argument type must match deduced parameter
  // type" rule apply here?
  pack_not_at_end<int*, double*>(t2, 0, 0, 0); // ok
}

```

The previous note said (before my changes):
```
deduced conflicting types for parameter 'Types' (<int *, double *> vs. <>)
````
The current note says (after my changesand also clang 14 would say this
if the pack was not trailing):
```
deduced conflicting types for parameter 'Types' (<int *, double *> vs. <int, int>)
```
GCC says: 
```
error: no matching function for call to ‘pack_not_at_end(std::tuple<int*, double*>&, int, int, int)’
   70 |     pack_not_at_end(t2, 0, 0, 9); // expected-error {{no match}}
````

---------

Co-authored-by: cor3ntin <corentinjabot@gmail.com>
Co-authored-by: Erich Keane <ekeane@nvidia.com>
@spaits spaits closed this as completed Jan 27, 2024
@spaits
Copy link
Contributor

spaits commented Jan 27, 2024

Thank you for reporting this issue.
It was fixed by #79371 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" rejects-valid
Projects
None yet
Development

No branches or pull requests

5 participants