Let us consider the following example source in C++:
template <class begin, class... end>
struct test {
template <end... beg_vals, begin end_val>
static consteval void make() {
}
};
int main() {
test<char, int, float>::make<4, 6.f, 'a'>();
}
Parameter packs can not be placed at the beginning of a template declaration, but here beg_vals
just expand a pack (at least thought so). I thought this program is valid, although is does not compile with either gcc
or msvc
.
gcc
's error message:
<source>: In function 'int main()':
<source>:9:46: error: no matching function for call to 'test<char, int, float>::make<4, 6.0e+0f, 'a'>()'
9 | test<char, int, float>::make<4, 6.f, 'a'>();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
<source>:4:27: note: candidate: 'template<end ...beg_vals, char end_val> static consteval void test<begin, end>::make() [with end ...beg_vals = {beg_vals ...}; begin end_val = end_val; begin = char; end = {int, float}]'
4 | static consteval void make() {
| ^~~~
<source>:4:27: note: template argument deduction/substitution failed:
<source>:9:46: error: conversion from 'float' to 'char' in a converted constant expression
9 | test<char, int, float>::make<4, 6.f, 'a'>();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
<source>:9:46: error: could not convert '6.0e+0f' from 'float' to 'char'
9 | test<char, int, float>::make<4, 6.f, 'a'>();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
| |
| float
<source>:9:46: error: wrong number of template arguments (3, should be 2)
<source>:4:27: note: provided for 'template<end ...beg_vals, char end_val> static consteval void test<begin, end>::make() [with end ...beg_vals = {beg_vals ...}; begin end_val = end_val; begin = char; end = {int, float}]'
4 | static consteval void make() {
| ^~~~
Compiler returned: 1
msvc
's error message:
example.cpp
<source>(4): error C3547: template parameter 'end_val' cannot be used because it follows a template parameter pack and cannot be deduced from the function parameters of 'test<begin,end...>::make'
<source>(3): note: see declaration of 'end_val'
<source>(4): note: the template instantiation context (the oldest one first) is
<source>(2): note: while compiling class template 'test'
Compiler returned: 2
At the same time, the program is accepted by modern clang++
.
I continued my experiment swapping beginning...
and end
parameter in make()
:
template <class begin, class... end>
struct test {
template <begin beg_val, end... end_vals>
static consteval void make() {
}
};
int main() {
test<char, int, float>::make<'a', 4, 6.f>();
}
This made msvc
to accept the code.
gcc
's error message changed, but it continued to "expect two template parameters" in make.
I am pretty sure that gcc
is at least wrong at its error message.
My questions are:
gcc
?Additionally:
Your GCC issues are likely all related in some form to GCC bug 84796, which has the following code:
template<typename... T>
struct S {
template<T... n>
static void f() { }
};
int main(){
S<int>::f<0>();
}
On trunk, this currently results in an ICE (https://godbolt.org/z/j1o9Ye8jY), and on GCC 14.2, it nonsensically rejects this code (https://godbolt.org/z/j1o9Ye8jY):
<source>: In function 'int main()':
<source>:8:17: error: no matching function for call to 'S<int>::f<0>()'
8 | S<int>::f<0>();
| ~~~~~~~~~~~~^~
<source>:4:17: note: candidate: 'template<T ...n> static void S<T>::f() [with T ...n = {n ...}; T = {int}]'
4 | static void f() { }
| ^
<source>:4:17: note: template argument deduction/substitution failed:
Note that in the example above, n
is simultaneously a parameter pack and a pack expansion ([temp.param] p17):
template <class... T> struct value_holder { template <T... Values> struct apply { }; // Values is a non-type template parameter pack }; // and a pack expansion
Therefore, all the usual rules for passing arguments to template parameters would apply, and e.g. in your last example, make<'a', 4, 6.f>
would pass 'a'
to end_val
, and both 4
and 6.f
to beg_vals
.
The last example is valid, and Clang and MSVC agree on that.
template <class begin, class... end> struct test { template <end... beg_vals, begin end_val> static consteval void make() { } }; int main() { test<char, int, float>::make<4, 6.f, 'a'>(); }
Since Clang accepts this and MSVC rejects this, it still needs to be answered whether that code is valid. To my knowledge, what applies here are the pack expansion mechanics in [temp.variadic], and [temp.arg.general] p1:
When the parameter declared by the template is a template parameter pack, it will correspond to zero or more template-arguments.
While the wording isn't super clear on this case, the intent appears to be that end... beg_vals
should let you provide a template-argument for each of the end
s expanded from the beg_vals
pack.
In other words, Clang should be correct, and MSVC falsely rejects this code.