【问题标题】:Handling gcc's noexcept-type warning处理 gcc 的 noexcept 类型警告
【发布时间】:2018-03-29 15:15:33
【问题描述】:

考虑这个例子,来自bug 80985

template <class Func>
void call(Func f)
{
    f();
}

void func() noexcept { }

int main()
{
    call(func);
}

在启用所有警告的情况下编译它,就像你做的那样,产生:

$ g++ -std=c++14 -Wall foo.cxx 
foo.cxx:2:6: warning: mangled name for ‘void call(Func) [with Func = void (*)() noexcept]’ will change in C++17 because the exception specification is part of a function type [-Wnoexcept-type]
 void call(Func f)
      ^~~~

我应该如何处理这个警告?解决方法是什么?

【问题讨论】:

  • 如果call 完全在您的项目内部,那没关系。仅在两个不同的翻译单元使用它的情况下才重要,其中一个是用 C++17 编译的,一个不是。即便如此,由于call 是一个模板函数,除了在最终的可执行文件中有一个额外的定义之外,它可能不会产生那么大的影响。
  • @DanielH 我不是说上面的 Barry,但是如果您使用 -wError 编译项目,那么“无害警告”将导致程序根本无法编译,尽管正确的。这很重要。
  • @markt1964 在帖子中只使用了-Wall。如果您使用-Werror 编译或尝试避免编译器错误(这是一个好主意),那么是的,您会遇到问题。可能最好通过添加-Wno-noexcept-type 来解决这个问题,具体取决于具体情况。
  • @Barry,当您切换编译器版本时,您是否从头开始重建您的项目(及其所有用户)?
  • 只是一个注释,但在 GCC8 中可能有 less of this warning。不确定它是否适用于您的具体情况。

标签: c++ g++ c++17 noexcept gcc7


【解决方案1】:

您可以对警告消息执行多种操作。

使用-Wno-noexcept-type 禁用它。在许多项目中,警告消息是无用的,因为生成的对象不可能与另一个期望它使用 GCC 的 C++17 名称修饰的对象链接。如果您没有使用不同的 -std= 设置进行编译,并且您没有构建一个静态或共享库,其中违规函数是其公共接口的一部分,则可以安全地禁用警告消息。

使用-std=c++17 编译所有代码。警告消息将消失,因为函数将使用新的损坏名称。

创建函数static。由于该函数不能再被另一个使用不同修饰函数的对象文件引用,因此不会显示警告消息。函数定义必须包含在使用它的所有编译单元中,但是对于像您的示例中这样的模板函数,这无论如何都是常见的。这也不适用于成员函数,static 表示其他含义。

在调用函数模板时,明确指定模板参数,以提供不具有异常规范的兼容函数指针类型。例如call&lt;void (*)()&gt;(func)。您也应该能够使用 cast 来执行此操作,但 GCC 7.2.0 仍会生成警告,即使使用 -std=c++17 不会更改重整。

当函数不是模板时,不要将noexcept 与函数类型中使用的任何函数指针类型一起使用。这一点和最后一点依赖于这样一个事实,即只有非抛出函数指针类型会导致命名重整更改,并且可以将非抛出函数指针分配 (C++11) 或隐式转换 (C++17) 以可能抛出函数指针。

【讨论】:

    【解决方案2】:

    我赞成罗斯对call&lt;void (*)()&gt;(func) 解决方案的回答。它明确告诉编译器您希望为非noexcept 函数类型实例化模板,并保证您的代码在 C++17 中的运行方式与在 C++14 中的运行方式完全相同。

    更多选择是:

    (1) 将 noexcept 函数包装在 lambda 中(不是 noexcept):

    template <class Func>
    void call(Func f)
    {
        f();
    }
    
    void func() noexcept { }
    
    int main()
    {
        call([]() { func(); });
    }
    

    (2) 创建一个没有noexcept 的单独包装函数。这最初需要更多的打字,但如果您有多个呼叫站点,它可以节省整体打字。这是最初提示我提交 GCC 错误的代码中的 what I ended up doing

    【讨论】:

      【解决方案3】:

      除了已经说过的之外,我在 GCC 7 中找到了消除此警告的另一种方法。显然,当且仅当 call() 的第一个实例化涉及 noexcept 时,GCC 才会生成此警告。因此,一个解决方案是首先使用非 noexcept 函数来实例化 call()

      这个技巧也可以:

      using dummy = decltype(call(std::declval<void(*)()>()));
      

      附:在这种情况下,GCC 8.2.1 不会报告警告。

      【讨论】:

        【解决方案4】:

        他们警告您的问题是,在 C++14 中,这将起作用:

        void call(void (*f)())
        {
            f();
        }
        
        void func() noexcept {}
        
        int main(int argc, char* argv[])
        {
            call(&func);
            return 0;
        }
        

        但在 C++17 中,您需要将 call 的声明更改为:

        void call(void (*f)() noexcept)
        {
            f();
        }
        

        由于您已将call 定义为模板,因此您无需担心这一点。不过,这可能会给您带来问题,因为推断的类型正在发生变化,这通常不会发生。

        例如,此代码将在 C++14 中编译,但在 C++17 中编译:

        void foo() noexcept {}
        void bar()          {}
        
        template <typename F>
        void call(bool b, F f1, F f2)
        {
            if (b)
                f1();
            else
                f2();
        }
        
        void foobar(bool b)
        {
            call(b, &foo, &bar);
        }
        

        在C++14中,foobar的类型是一样的,但是在C++17中是不同的,意味着模板解析会失败。 gcc 7.2 中带有-std=c++1z 标志的错误消息是:

        note:   template argument deduction/substitution failed:
        note:   deduced conflicting types for parameter 'F' ('void (*)() noexcept' and 'void (*)()')
        

        在您给出的示例中,没有问题,在 C++14 或 C++17 模式下编译也不会有问题。如果代码比这里的示例更复杂(例如,类似于我上面给出的示例),您可能会遇到一些编译器问题。看来你有一个最近的编译器;尝试使用-std=c++1z 进行编译,看看是否有警告或错误。

        【讨论】:

        • 这不能回答问题。我知道在类型系统中添加了noexcept
        • 我在最后添加了更多解释;希望能澄清问题
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-27
        • 1970-01-01
        • 2010-11-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多