Reputation: 131640
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...);
}
};
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.
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.
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.
Upvotes: 2
Views: 119
Reputation: 170153
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.
Upvotes: 3