From [temp.variadic] (working draft) it seemed to me that a parameters pack can be expanded while defining an arguments list of another template class or function.
Consider the following class:
template<typename... T>
struct S {
template<T... I>
void m() {}
};
int main() {
S<int, char> s;
// ...
}
The intent is to capture the types used to specialize the template class S and use them to define an arguments list of non-type parameters for the member method m (T is limited to a few types, of course, but this isn't the argument of the question).
Is this legal code? Can I use a parameter pack the way I used it or am I misinterpreting the standard (pretty sure that's the case indeed)?
In order to add more details to the question, here are some results from a few experiments with the major compilers:
s.m<0, 'c'>(): clang v3.9 compiles it, GCC v6.2 and GCC v7 return an error.
s.m<0>();: clang v3.9 compiles it, GCC v6.2 returns an error and GCC v7 stops the compilation with an ICE.
s.m<>();: clang v3.9, GCC v6.2 and GCC v7 compile it with no errors.
At least, compilers seem to be as confused as me.
The definition of the template S, and the instantiation of S<int, char>, are valid.
See [temp.param]/15: "A template parameter pack that is a parameter-declaration whose type contains one or more unexpanded parameter packs is a pack expansion."
This means that template<T ...I> can mean one of two different things: if T is a non-pack type, then it declares a normal parameter pack, accepting any number of Ts. However, if T contains an unexpanded parameter pack, then the parameter declaration is instead expanded into a sequence of parameters when the outer template is instantiated.
Your first call to m is valid, but your second and third calls to m are ill-formed
The instantiation of S<int, char> looks like this:
template<>
struct S<int, char> {
template<int I$0, char I$1>
void m() {}
};
(where I$0 and I$1 are the first and second slices of the pack I).
Therefore (because neither I$0 nor I$1 can be deduced from a call to m), s.m<0,'c'>() is valid but s.m<0>() and s.m<>() are ill-formed.
Related
I'm learning C++ using the books listed here. In particular I read about variadic templates. Now, to further clear my concepts I'm also writing simple examples and trying to understand them by myself using debugger and cout statements.
One such program that compiles with gcc but is rejected by clang is given below. Demo.
template<typename T, typename... V>
struct C
{
T v(V()...);;
};
int main()
{
C<int> c; //works with gcc but rejected in clang
C<int, double, int, int> c2; //same here: works with gcc but rejected in clang
}
So my question is which compiler is right here(if any)?
Here is the error that clang gives:
<source>:6:12: error: '...' must be innermost component of anonymous pack declaration
T v(V()...);;
^~~
...
1 error generated.
Compiler returned: 1
This is a GCC bug. The correct syntax is T v(V...());, which Clang accepts and GCC rejects (incorrectly).
In a function parameter list, the ... that expands a pack must precede the parameter name, or be in the place where the name would otherwise be. This is more commonly seen in cases like V &&... v.
The related grammar is: function-definition -> declarator -> parameters-and-qualifiers -> ... -> parameter-declaration -> abstract-declarator ("abstract" = no parameter name).
[dcl.fct]/26 says that if there's an ambiguity in a function parameter list whether ... is a pack expansion or a C-style variadic parameter, it resolves to a pack expansion. But in this case there's no ambiguity, V()... leaves the pack unexpanded (which should be a compilation error) and ... should be a C-style variadic parameter.
GCC is wrong in accepting the program because a function parameter pack is introduced using an ellipsis (...) prior
to (or in the place of) the function parameter name.
This means in your example the correct way to declare the function with parameter of type pointer to function is T v(V...());. But note that gcc rejects this modified code. Demo
Here is the gcc bug report:
GCC accepts invalid program involving function declaration with pack expansion
Note
It quite interesting to note that gcc rejects T v(V...()) but accepts T v(V...b()). Demo
template<typename T, typename... V>
struct C
{
//-------vvv---------->this is the correct way which gcc rejects
T v(V...());
//-------vvv---------->but gcc accepts this when we name the parameter
T v(V...b());
};
I came across the following statement in the standard:
If a template-parameter is a type-parameter with an ellipsis prior to its optional identifier or is a parameter-declaration that declares a pack ([dcl.fct]), then the template-parameter is a template parameter pack. A template parameter pack that is a parameter-declaration whose type contains one or more unexpanded packs is a pack expansion. ... A template parameter pack that is a pack expansion shall not expand a template parameter pack declared in the same template-parameter-list.
(end quote)
So consider the following invalid example:
template<typename... Ts, Ts... vals> struct mytuple {}; //invalid
The above example is invalid because the template type parameter pack Ts cannot be expanded in its own parameter list.
Are the below given examples valid/invalid?
Then i tried the same with function templates and expected the same result but to my surprise it compiles fine in gcc & clang but not in msvc. The example is as follows:
//is this valid?
template<typename... T, T... ar>
void func(){}
int main()
{
}
Similarly, the below given example compiles in gcc and clang but not in msvc:
//is this valid?
template<typename...T, int (*FUNC)(T...)>
int wrapper(T... args) { return (*FUNC)(args...) * 10; }
int main()
{
}
Which compiler is right here? That is, does the quoted statement temp.param#17 applies to the given two examples and so they are invalidated or is the quote not applicable to the given two examples.
It's a bug in GCC and Clang. You wrote clearly-defined-as-invalid code, MSVC correctly throws an error while gcc and clang do not. Therefore MSVC is correct. GCC and Clang are not.
As an aside, I ran the code snippets in MSVC C++20 mode and the same error was still thrown. It's been correctly handled since C++14 in MSVC and a bug in GCC and Clang for just as long.
I have tried to construct a case that requires no typename or template, but still yield a variable or template depending on whether a given name t is a function parameter pack or not
template<typename T> struct A { template<int> static void f(int) { } };
template<typename...T> struct A<void(T...,...)> { static const int f = 0; };
template<typename> using type = int;
template<typename T> void f(T t) { A<void(type<decltype(t)>...)>::f<0>(1); }
int main() {
f(1);
}
The above will refer to the static const int, and do a comparison. The following just has T t changed to be a pack and make f refer to a template, but GCC does not like either
template<typename ...T> void f(T ...t) { A<void(type<decltype(t)>...)>::f<0>(1); }
int main() {
f(1, 2, 3);
}
GCC complains for the first
main.cpp:5:68: error: incomplete type 'A<void(type<decltype (t)>, ...)>' used in nested name specifier
template<typename T> void f(T t) { A<void(type<decltype(t)>...)>::f<0>(1); }
And for the second
main.cpp:5:74: error: invalid operands of types '<unresolved overloaded function type>' and 'int' to binary 'operator<'
template<typename ...T> void f(T ...t) { A<void(type<decltype(t)>...)>::f<0>(1); }
I have multiple questions
Does the above code work according to the language, or is there an error?
Since Clang accepts both variants but GCC rejects, I wanted to ask what compiler is correct?
If I remove the body of the primary template, then for the f(1, 2, 3) case, Clang complains
main.cpp:5:42: error: implicit instantiation of undefined template 'A<void (int)>'
Please note that it says A<void (int) >, while I would expected A<void (int, int, int)>. How does this behavior occur? Is this a bug in my code - i.e is it illformed, or is it a bug in Clang? I seem to remember a defect report about the order of expansion vs the substitution of alias template, is that relevant and does it render my code ill-formed?
Expanding a parameter pack either should, or does, make an expression type dependent. Regardless of whether the things expanded are type dependent.
If it did not, there would be a gaping hole in the type dependency rules of C++ and it would be a defect in the standard.
So A<void(type<decltype(t)>...)>::f when t is a pack, no matter what tricks you pull in the void( here ) parts to unpack the t, should be a dependent type, and template is required before the f if it is a template.
In the case where t is not a pack, it is intended that type<decltype(t)> not be dependent (See http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390), but the standard may or may not agree at this point (I think not?)
If compilers did "what the committee intended", then when t is not a pack:
A<void(type<decltype(t)>...)>::f<0>(1)
could mean
A<void(int...)>::f<0>(1)
which is
A<void(int, ...)>::f<0>(1)
and if f is a template (your code makes it an int, but I think swapping the two should work) this would be fine. But the standard apparently currently disagrees?
So if http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390 was implemented, then you could swap your two A specializations. The void(T...,...) specialization should have a template<int> void f(int), and the T specialization should have a static const int.
Now in the case where A<> is dependent (on the size of a pack), ::f is an int and does not need template. In the case where A<> is not dependent, ::f is a template but does not need disambiguation.
We can replace the type<decltype(t)>... with:
decltype(sizeof(decltype(t)*))...
and sizeof(decltype(t)*) is of non-dependent type (it is std::size_t), decltype gives us a std::size_t, and the ... is treated as a old-school ... arg. This means void(std::size_t...) becomes a non-dependent type, so A<void(std::size_t...)> is not dependent, so ::f being a template is not a template in a dependent context.
In the case where t is a parameter pack with one element
decltype(sizeof(decltype(t)*))...
becomes
std::size_t
but in a dependent context (one copy per element in t pack). So we get
A<void(std::size_t)>::f
which is presumed to be a scalar value, so
A<void(std::size_t)>::f<0>(1)
becomes an expression evaluating to false.
(Chain of logic generated in a discussion with Johannes in comments in original question).
Your second case is ill-formed; A<void(type<decltype(t)>...)>::f<0>(1) should be
A<void(type<decltype(t)>...)>::template f<0>(1)
// ~~~~~~~~~
For the first case, both compilers are behaving incorrectly; this was considered sufficiently confusing that CWG 1520 was raised to query the correct behavior; the conclusion was that pack expansion should be applied before alias substitution:
The latter interpretation (a list of specializations) is the correct interpretation; a parameter pack can't be substituted into anything, including an alias template specialization. CWG felt that this is clear enough in the current wording.
This is reminiscent of CWG 1558 (alias templates and SFINAE), which was fixed for C++14, but per the above even C++11 compilers are expected to get this correct, so it is disappointing that gcc and clang get it wrong (though in fairness they do behave correctly in simpler cases, including the motivating example in CWG 1520). Note that MSVC had a similar bug till recently; it is fixed in VS2015.
Your code (only in the first case) is correct; but as a workaround, you could alter your alias template to use and discard its template parameter, fixing your program for both compilers - of course that means that your CWG 1390 exploit will cease to be valid:
template<typename T> using type = decltype(((int(*)(T*))(0))(0)); // int
However, I don't think your CWG 1390 trick can work as presented, since even though the expansion-substitution of type<decltype(t)>... is not dependent on the types of t..., it is dependent on their number:
template<typename T> struct A { template<int> static void f(int) {} };
template<> struct A<void(int, int, int)> { static const int f = 0; };
As Yakk points out, it can be made to work if you swap the member function template and data member, since a data member is OK in dependent context.
From [temp.variadic] (working draft) it seemed to me that a parameters pack can be expanded while defining an arguments list of another template class or function.
Consider the following class:
template<typename... T>
struct S {
template<T... I>
void m() {}
};
int main() {
S<int, char> s;
// ...
}
The intent is to capture the types used to specialize the template class S and use them to define an arguments list of non-type parameters for the member method m (T is limited to a few types, of course, but this isn't the argument of the question).
Is this legal code? Can I use a parameter pack the way I used it or am I misinterpreting the standard (pretty sure that's the case indeed)?
In order to add more details to the question, here are some results from a few experiments with the major compilers:
s.m<0, 'c'>(): clang v3.9 compiles it, GCC v6.2 and GCC v7 return an error.
s.m<0>();: clang v3.9 compiles it, GCC v6.2 returns an error and GCC v7 stops the compilation with an ICE.
s.m<>();: clang v3.9, GCC v6.2 and GCC v7 compile it with no errors.
At least, compilers seem to be as confused as me.
The definition of the template S, and the instantiation of S<int, char>, are valid.
See [temp.param]/15: "A template parameter pack that is a parameter-declaration whose type contains one or more unexpanded parameter packs is a pack expansion."
This means that template<T ...I> can mean one of two different things: if T is a non-pack type, then it declares a normal parameter pack, accepting any number of Ts. However, if T contains an unexpanded parameter pack, then the parameter declaration is instead expanded into a sequence of parameters when the outer template is instantiated.
Your first call to m is valid, but your second and third calls to m are ill-formed
The instantiation of S<int, char> looks like this:
template<>
struct S<int, char> {
template<int I$0, char I$1>
void m() {}
};
(where I$0 and I$1 are the first and second slices of the pack I).
Therefore (because neither I$0 nor I$1 can be deduced from a call to m), s.m<0,'c'>() is valid but s.m<0>() and s.m<>() are ill-formed.
I don't understand how the following feature should be used. When I call A::f I can omit the template parameters, but I don't understand why.
template <typename... Args>
struct A
{
template <Args...>
void f() {}
};
int main()
{
A<int, bool> a;
a.f();
}
To be specific, what does the template <Args...> mean and why can I leave the template parameters out of the function call to f?
template<typename ...Args> is a variadic template. It means, that you can specify any number of template type parameters, but I think you already know this.
Whenever Args... appears in the template code, it will be unpacked ("expanded") to the types of the instantiation. In your example, this is int, bool. So your class becomes, when fully expanded, this definition:
struct A<int, bool>
{
template <int, bool>
void f() {}
};
This means, A<int,bool>::f() is again templated (the arguments are unpacked into another template declaration, as you called it), but this time with non-type template parameters with the types int and bool (they're anonymous), so you could instantiate f() for example like this:
a.f<1, true>();
Unfortunately, g++ seems to have a bug and won't accept this piece of code, while it accepts your code.
clang accepts both codes. I expect that in your code, clang doesn't care if the int and bool template parameters are omitted, but it doesn't complain either when they are specified (in contrast to g++).
Usage example:
If you want to use the specified values, they can't be anonymous (obviously). You can, for example, provide a format string in f() which is used to "printf" the template values, like this:
template <Args ...values>
void f(const char *fmt) {
std::printf(fmt, values...);
}
Then, the following code
A<int> a;
a.f<42>("The answer is %d!\n");
will print:
The answer is 42!
But, using the syntax from above (Args... being expanded to anonymous non-type template parameters), the template parameters are essentially useless.
Without specifying values, it still compiles (which surprised me!) and prints an uninitialized int value.
The reason you can leave the template arguments out is because c++11 has template argument deduction template<args ...> is a variadic template which is basically a pack of possibly different typenames. This website explains the omission of template arguments. From reading further into the deduction of non type template arguments, which the compiler can deduce I believe that the compiler is realizing that they are never used (with clang at least) and making it's deduction based on that, read the deducing non type template arguments portion of the website