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());
};
Related
I am learning C++ using the resources listed here. In particular, I have learnt that in C++20 we can have a class type as a non type template parameter. Now, to better understand the concept, I tried the following example that is accepted by msvc and gcc but rejected by clang. My question is which compiler is right?
Demo
struct Impl
{
constexpr Impl(std::initializer_list<int>)
{
}
};
struct Bar{};
template<typename T, Impl impl>
struct Foo
{
};
int main()
{
constexpr Foo<Bar, {1,2,3,4}> foo; //works in msvc & gcc but rejected in clang
return 0;
}
GCC and MSVC are wrong in accepting the program as it is ill-formed for the reason explained below.
The standard doesn't allow braced init list {1,2,3,4} to be a template argument. This can be seen from temp.names#1:
template-argument:
constant-expression
type-id
id-expression
And since {1,2,3,4} is not any of the above three listed constructs, it cannot be used as a template argument.
Additionally note that {1,2,3,4} is not an expression and does not have a type.
This is the reason clang generates the error saying:
vvvvvvvvvvvvvvvvvvv
error: expected expression
constexpr Foo<Bar, {1,2,3,4}> foo;
The gcc bug has been reported as:
GCC accepts invalid program involving {1,2,3,4} as template argument
And msvc bug as:
MSVC accepts invalid program involving {1,2,3,4} as template argument
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 stumbled on a strange interaction between typedef and variadic template parameters that I'd like to understand. The following code compiles with clang but gives an error with GCC:
template<typename T> // no error if this is not a template
struct Traits;
#pragma GCC diagnostic ignored "-Wunused-parameter"
template<typename ...args>
void function(args... e) {}
template<typename T>
struct Caller {
typedef typename Traits<T>::types traits_types; // no error if this is changed to a 'using' directive
template<typename ...types> // no error if the pack is converted to a single parameter
static void method(types... e) {
function<traits_types>(e...);
}
};
GCC behavior
When I compile (not link) this with GCC, I get an error on line 14:
$ g++-9.2.0 -Wall -Wpedantic -Wextra -std=c++11 -c test.cpp
test.cpp: In static member function ‘static void Caller<T>::method(types ...)’:
test.cpp:14:31: error: parameter packs not expanded with ‘...’:
14 | function<traits_types>(e...);
| ~~~~~~~~~~~~~~~~~~~~~~^~~~~~
test.cpp:14:31: note: ‘types’
$
It acts as if GCC first substitutes in the definition of traits_types, which would produce
template<typename ...types>
static void method(types... e) {
function<typename Traits<T>::types>(e...);
}
and then evaluates template parameter substitution, at which point it sees the last occurrence of the token types as an unexpanded parameter pack and produces an error accordingly.
I've tested this with GCC 6.4.0, 7.3.0, 8.2.0, 8.3.0, 9.1.0, and 9.2.0, as well as a couple older versions, and the behavior is consistent among all of them.
clang behavior
However, when I compile this with clang, it works fine.
$ clang++-8 -Wall -Wpedantic -Wextra -std=c++11 -c test.cpp
$
This seems as if it first substitutes in for the parameter pack types and then handles the name traits_types.
I see this behavior consistently in clang 6.0.0, 7.0.1, and 8.0.1, and a couple older versions.
Question
In standard C++11, is GCC correct to give the error that it does, or is the code valid? Or is it undefined/implementation-defined/otherwise unspecified?
I've looked through much of cppreference.com's content on templates and typedefs without finding anything that clearly addresses this case. I also checked several other questions (1, 2, 3, 4, 5, etc.), all of which seem similar but don't quite apply to this situation, as far as I can tell.
If this is in fact a compiler bug, a link to a relevant issue in the bug tracker confirming that GCC (or clang, if applicable) doesn't handle this correctly would settle the question pretty well.
Yes it's a bug. What you observed as "it acts as if GCC first substitutes in the definition of traits_types", is followed by a manifestation of GCC bug 90189:
Source:
struct A {
using CommonName = char;
};
template <typename T, typename... CommonName>
struct B {
using V = typename T::CommonName;
};
template struct B<A>;
Output:
<source>:7:37: error: parameter packs not expanded with '...':
7 | using V = typename T::CommonName;
| ^
<source>:7:37: note: 'CommonName'
Compiler returned: 1
Rejected by all GCC versions. Accepted by clang, msvc.
GCC acts like you wrote typename Traits<T>::types directly, and then it gets confused by the dependent name types being the same as the name of the template parameter pack. You can get around it by giving the pack a different name, but in standard C++ the dependent name can be the same as the name of the pack. Because one must be qualified, whereas the other is unqualified, there should be no ambiguity.
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.
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.