Reputation: 1162
Edit - The exact minimal code to test this was in my original post below, but it is two chunks. My apologies, here it is in one piece. Commenting out the instantiations in main
and then uncommenting them one at a time shows the behavior I describe in this post.
template <typename... TsOuter>
struct Outer
{
template <TsOuter...>
static void InnerFunc() {};
};
int main(int argc, char** argv)
{
Outer<int, int>::InnerFunc<1, 1>(); // Should work. Works on MSVC, fails on g++
Outer<int, int>::InnerFunc(); // Should fail. Works on both compilers
Outer<int, int>::InnerFunc<>(); // Should fail. Works on both compilers
Outer<int, int>::InnerFunc<1>(); // Should fail. Works on MSVC, fails on g++
Outer<int, int>::InnerFunc<1, 1, 1>(); // Should fail. Fails on both compilers
Outer<int, int>::InnerFunc<nullptr, nullptr>(); // Should fail. Fails on both compilers.
}
Edit - I failed to include the compiler versions I am using, with which I see the behavior I describe:
I am seeing compiler behaviors that I believe are wrong - in both Microsoft C++ and g++. The compilers give errors when I believe they should compile successfully, and they compile successfully when I believe they should give errors. The buggy (?) compiler behaviors are not exactly the same between the two compilers.
My question is: are the compilers indeed buggy, or is there a bug in my understanding somewhere? Does the spec say that compiler behavior for the code below is undefined?
The issue occurs when a template class with a template parameter that is a parameter pack defines a member function template that uses the class's parameter pack as its template parameter. When I instantiate that member function template
And what is weird, if the member template is a member template class instead of a member template function, everything works exactly as I expect.
Here is the template class definition:
template <typename... TsOuter>
struct Outer
{
template <TsOuter...>
struct InnerClass {};
template <TsOuter...>
static void InnerFunc() {};
};
Notice the template parameters of InnerClass
and InnerFunc
depend on the template arguments of Outer
.
I instantiate Outer
as Outer<int, int>
. That makes the declaration of InnerClass
template <int,int>
struct InnerClass {};
and the declaration of InnerFunc
template <int, int>
static void InnerFunc() {};
When instantiating InnerClass
, the compilers' behavior (both MSVC and g++) matches my understanding:
Outer<int, int>::InnerClass<1, 1> x2 {}; // OK
Outer<int, int>::InnerClass x2 {}; // ERROR - No template arguments provided for InnerClass
Outer<int, int>::InnerClass<> x2 {}; // ERROR - <> does not match <int, int> (to few template arguments)
Outer<int, int>::InnerClass<1> x1 {}; // ERROR - <1> does not match <int, int> (to few template arguments)
Outer<int, int>::InnerClass<1, 1, 1> x3 {}; // ERROR - <1,1,1> does not match <int, int> (to many template arguments)
Outer<int, int>::InnerClass<nullptr, nullptr> x4 {}; // ERROR - <nullptr, nullptr> does not match <int, int> (template argument types do not match template parameter types)
However, with InnerFunction
it is different. My expectations for InnerFunction
are the same for InnerClass
. But what I see is:
Outer<int, int>::InnerFunc<1,1>(); // Should work. Works on MSVC, fails on g++
Outer<int, int>::InnerFunc(); // Should fail. Works on both compilers
Outer<int, int>::InnerFunc<>(); // Should fail. Works on both compilers
Outer<int, int>::InnerFunc<1>(); // Should fail. Works on MSVC, fails on g++
Outer<int, int>::InnerFunc<1,1,1>(); // Should fail. Fails on both compilers
Outer<int, int>::InnerFunc<nullptr, nullptr>(); // Should fail. Fails on both compilers.
If Outer
has non-variadic template parameter(s), I don't see this issue - everything works as expected for both InnerClass
and InnerFunction
.
Given the behavior of the compilers that I describe here, am I correct that they both have bugs? If so they seem like pretty significant bugs to me.
Thanks!
Upvotes: 3
Views: 131
Reputation: 40891
This is indeed a compiler error in gcc.
The following was used to test
template <typename... TsOuter>
struct Outer
{
template <TsOuter... n>
static void InnerFunc() {
static int i[2] = { n... };
};
};
int main() {
typedef Outer<int, int> t;
t::InnerFunc<1, 1>();
}
This compiles fine in clang and msvc.
But in gcc the error is:
<source>:13:24: error: no matching function for call to 'Outer<int, int>::InnerFunc<1, 1>()'
13 | t::InnerFunc<1, 1>();
| ^
<source>:5:17: note: candidate: 'template<TsOuter ...n> static void Outer<TsOuter>::InnerFunc() [with TsOuter ...n = {n ...}; TsOuter = {int, int}]'
5 | static void InnerFunc() {
| ^~~~~~~~~
<source>:5:17: note: template argument deduction/substitution failed:
<source>:13:24: error: wrong number of template arguments (2, should be 1)
13 | t::InnerFunc<1, 1>();
| ^
Which suggests that gcc thinks that there should be one argument, not 2, mistakenly not expanding the parameter pack.
And if you do actually pass one argument instead of 2:
t::InnerFunc<1>();
<source>:13:21: internal compiler error: tree check: accessed elt 1 of tree_vec with 0 elts in tsubst_pack_expansion, at cp/pt.c:12169
13 | t::InnerFunc<1>();
| ^
I'm not actually sure how parameter packs as template parameters is supposed to work, but it definitely shouldn't be an internal compiler error. I couldn't reproduce this on a local g++ install, but the error wierdly cuts off (Printing "template argument deduction/substitution failed:" and then nothing)
Upvotes: 5