实例化成员模板函数时的Buggy(?)编译器行为

时间:2019-01-11 17:10:25

标签: c++

编辑-在下面的原始帖子中测试此的确切最小代码是,但这是两个大块。抱歉,这里是一件。注释掉main中的实例化,然后一次将它们取消注释就可以看出我在这篇文章中描述的行为。

template <typename... TsOuter>
struct Outer
{
    template <TsOuter...>
    static void InnerFunc() {};
};

int main(int argc, char** argv)
{   
    Outer<int, int>::InnerFunc<1, 1>();              // Should work.  Works on MSVC, fails on g++
    Outer<int, int>::InnerFunc();                    // Should fail.  Works on both compilers
    Outer<int, int>::InnerFunc<>();                  // Should fail.  Works on both compilers
    Outer<int, int>::InnerFunc<1>();                 // Should fail.  Works on MSVC, fails on g++
    Outer<int, int>::InnerFunc<1, 1, 1>();           // Should fail.  Fails on both compilers
    Outer<int, int>::InnerFunc<nullptr, nullptr>();  // Should fail.  Fails on both compilers.
}

编辑-我无法包含我正在使用的编译器版本,由此可以看到我描述的行为:

  • gcc:7.3.0
  • MSVC:Microsoft Visual Studio Professional 2017,版本15.7.4

我看到了我认为错误的编译器行为-在Microsoft C ++和g ++中。我认为编译器应该成功编译时会给出错误,而当我认为应该给出错误时它们就会成功编译。越野车(?)编译器的行为在两个编译器之间并不完全相同。

我的问题是:编译器确实有错误,还是我所理解的某个地方有错误?规范是否表示未定义以下代码的编译器行为?

当带有作为参数包的模板参数的模板类定义成员函数模板,该成员函数模板使用该类的参数包作为其模板参数时,就会发生此问题。当我实例化该成员函数模板时

  • 当其模板参数正确匹配其模板参数的定义时,我会出错
  • 反之亦然:当其模板参数与模板参数不匹配时,我会成功编译。

奇怪的是,如果成员模板是成员模板 class 而不是成员模板函数,那么一切都将按照我的预期工作。

这是模板类定义:

template <typename... TsOuter>
struct Outer
{
    template <TsOuter...>
    struct InnerClass {};

    template <TsOuter...>
    static void InnerFunc() {};
};

请注意InnerClassInnerFunc的模板参数取决于Outer的模板参数。

我将Outer实例化为Outer<int, int>。这样就声明了InnerClass

template <int,int>
struct InnerClass {};

InnerFunc

的声明
template <int, int>
static void InnerFunc() {};

实例化InnerClass时,编译器的行为(MSVC和g ++)都符合我的理解:

Outer<int, int>::InnerClass<1, 1> x2 {};              // OK
Outer<int, int>::InnerClass x2 {};                    // ERROR - No template arguments provided for InnerClass
Outer<int, int>::InnerClass<> x2 {};                  // ERROR - <> does not match <int, int> (to few template arguments)
Outer<int, int>::InnerClass<1> x1 {};                 // ERROR - <1> does not match <int, int> (to few template arguments)
Outer<int, int>::InnerClass<1, 1, 1> x3 {};           // ERROR - <1,1,1> does not match <int, int> (to many template arguments)
Outer<int, int>::InnerClass<nullptr, nullptr> x4 {};  // ERROR - <nullptr, nullptr> does not match <int, int> (template argument types do not match template parameter types)

但是,与InnerFunction不同。我对InnerFunction的期望与InnerClass相同。但是我看到的是:

Outer<int, int>::InnerFunc<1,1>();               // Should work.  Works on MSVC, fails on g++
Outer<int, int>::InnerFunc();                    // Should fail.  Works on both compilers
Outer<int, int>::InnerFunc<>();                  // Should fail.  Works on both compilers
Outer<int, int>::InnerFunc<1>();                 // Should fail.  Works on MSVC, fails on g++
Outer<int, int>::InnerFunc<1,1,1>();             // Should fail.  Fails on both compilers
Outer<int, int>::InnerFunc<nullptr, nullptr>();  // Should fail.  Fails on both compilers.

如果Outer具有非可变模板参数,那么我看不到此问题-一切对InnerClassInnerFunction均正常。

鉴于我在此描述的编译器的行为,我是否纠正它们都存在错误?如果是这样,它们对我来说似乎是非常重要的错误。

谢谢!

1 个答案:

答案 0 :(得分:4)

这确实是gcc中的编译器错误。

以下内容用于测试

https://godbolt.org/z/8ZZt-B

template <typename... TsOuter>
struct Outer
{
    template <TsOuter... n>
    static void InnerFunc() {
        static int i[2] = { n... };
    };
};

int main() {
    typedef Outer<int, int> t;

    t::InnerFunc<1, 1>();
}

这在clang和msvc中可以正常编译。

但是在gcc中,错误是:

<source>:13:24: error: no matching function for call to 'Outer<int, int>::InnerFunc<1, 1>()'
   13 |     t::InnerFunc<1, 1>();
      |                        ^
<source>:5:17: note: candidate: 'template<TsOuter ...n> static void Outer<TsOuter>::InnerFunc() [with TsOuter ...n = {n ...}; TsOuter = {int, int}]'
    5 |     static void InnerFunc() {
      |                 ^~~~~~~~~
<source>:5:17: note:   template argument deduction/substitution failed:
<source>:13:24: error: wrong number of template arguments (2, should be 1)
   13 |     t::InnerFunc<1, 1>();
      |                        ^

哪个建议gcc认为应该有一个参数,而不是2,错误地没有扩展参数包。

如果您确实传递了一个参数而不是2:

https://godbolt.org/z/9fZZZC

t::InnerFunc<1>();
<source>:13:21: internal compiler error: tree check: accessed elt 1 of tree_vec with 0 elts in tsubst_pack_expansion, at cp/pt.c:12169
   13 |     t::InnerFunc<1>();
      |                     ^

我实际上不确定参数如何作为模板参数打包,但是绝对不应该是内部编译器错误。我无法在本地g ++安装上重现此错误,但错误会被切断(打印“模板参数推导/替换失败:”,然后什么也没有)