David Z
David Z

Reputation: 131640

GCC error when typedef name coincides with variadic template parameter name

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.

Upvotes: 2

Views: 119

Answers (1)

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

Related Questions