当 typedef 名称与可变参数模板参数名称一致时出现 GCC 错误

GCC error when typedef name coincides with variadic template parameter name

我偶然发现了 typedef 和我想了解的可变参数模板参数之间的奇怪交互。以下代码使用 clang 编译但使用 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 行为

当我用 GCC 编译(而不是 link)时,第 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’
$

它的作用就好像 GCC 首先替换 traits_types 的定义,这将产生

template<typename ...types>
static void method(types... e) {
    function<typename Traits<T>::types>(e...);
}

然后评估模板参数替换,此时它将最后一次出现的标记 types 视为未扩展的参数包,并相应地产生错误。

我已经使用 GCC 6.4.0、7.3.0、8.2.0、8.3.0、9.1.0 和 9.2.0 以及几个旧版本对此进行了测试,行为是一致的在他们当中。

当当行为

然而,当我用 clang 编译它时,它工作正常。

$ clang++-8 -Wall -Wpedantic -Wextra -std=c++11 -c test.cpp
$

这似乎首先替换参数包 types 然后 处理名称 traits_types.

我在 clang 6.0.0、7.0.1 和 8.0.1 以及几个旧版本中始终看到这种行为。

问题

在标准 C++11 中,GCC 给出的错误是否正确,或者代码是否有效?还是 undefined/implementation-defined/otherwise 未指定?

我浏览了很多 cppreference.com's content on templates and typedefs without finding anything that clearly addresses this case. I also checked several other questions (, 2, 3, , ,等等),所有这些看起来都很相似,但据我所知并不完全适用于这种情况。

如果这实际上是一个编译器错误,link 到错误跟踪器中的相关问题确认 GCC(或 clang,如果适用)没有正确处理这个问题会很好地解决这个问题.

是的,这是一个错误。您观察到的 "it acts as if GCC first substitutes in the definition of traits_types" 之后会出现 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 就像你直接写 typename Traits<T>::types 一样,然后它会因为依赖名称 types 与模板参数包的名称相同而感到困惑。您可以通过给包一个不同的名称来绕过它,但在标准 C++ 中,从属名称可以与包的名称相同。因为一个必须合格,一个不合格,所以不能有歧义。