借助static_assert改进诊断

时间:2013-12-02 18:13:00

标签: c++ templates c++11 diagnostics static-assert

在模板编程中,static_assert帮助程序员检查模板参数上的约束,并在违反约束时生成人类可读错误消息。

考虑这段代码,

template<typename T>
void f(T)
{
    static_assert(T(), "first requirement failed to meet.");

    static_assert(T::value, "second requirement failed to meet.");    

    T t = 10; //even this may generate error!
}

我的想法是:如果第一个static_assert失败,则意味着T上的某些要求不符合,因此编译应该停止,仅生成第一个错误消息 - 因为继续编译只是为了生成越来越多的错误消息没有多大意义,其中大多数经常指向单个约束违规。数百条错误信息,而不仅仅是一条,在屏幕上看起来非常可怕 - 我甚至会说,它在某种程度上违背了static_assert目的

例如,如果我将上述功能模板称为:

f(std::false_type{});

GCC 4.8生成以下内容:

main.cpp: In instantiation of 'void f(T) [with T = std::integral_constant<bool, false>]':
main.cpp:16:24:   required from here
main.cpp:7:5: error: static assertion failed: first requirement failed to meet.
     static_assert(T(), "first requirement failed to meet.");
     ^
main.cpp:9:5: error: static assertion failed: second requirement failed to meet.
     static_assert(T::value, "second requirement failed to meet.");    
     ^
main.cpp:11:11: error: conversion from 'int' to non-scalar type 'std::integral_constant<bool, false>' requested
     T t = 10; //even this may generate error!

正如你所看到的那样(online),这太错误了。如果第一个static_assert失败,如果编译继续,很可能其余的代码也会失败,那么为什么要继续编译?在模板编程中,我确信许多程序员不需要这样的级联错误消息!

我尝试通过将函数拆分为多个函数来解决这个问题,每次只检查一个约束,如下:

template<typename T>
void f_impl(T); //forward declaration

template<typename T>
void f(T)
{
    static_assert(T(), "first requirement failed to meet.");
    f_impl(T());
}

template<typename T>
void f_impl(T)
{
    static_assert(T::value, "second requirement failed to meet.");     
    T t = 10;
}  

f(std::false_type{}); //call

现在生成这个:

main.cpp: In instantiation of 'void f(T) [with T = std::integral_constant<bool, false>]':
main.cpp:24:24:   required from here
main.cpp:10:5: error: static assertion failed: first requirement failed to meet.
     static_assert(T(), "first requirement failed to meet.");
     ^

这是一个很大的改进 - 只有一个错误信息更易于阅读和理解(参见online)。

我的问题是,

  • 为什么汇编不会在第一个static_assert停止?
  • 由于功能模板拆分并检查每个function_impl中的一个约束,帮助 GCC和clang 仍然 generates lots of error,有没有办法改善诊断更一致的方式 - 适用于所有编译器的东西?

2 个答案:

答案 0 :(得分:4)

这里需要平衡多个目标。特别是,通过停止第一个错误可以获得更小的更简单的错误消息,这是好的。同时,在尝试另一个可能很昂贵的编译之前,停止第一个错误并不会提供有关您可能想要解决的任何其他问题的信息。例如,在您的第一个示例中,我个人更喜欢立即检查所有static_assert。阅读错误消息:

您未能满足以下要求

  • 默认构造函数
  • 嵌套value类型

我宁愿在第一次传递中检测到这两个错误,而不是修复一个,并且必须花几分钟让构建系统在下一个传递中跳闸。

这里的前提是编译器能够从错误中恢复并继续解析,尽管语法是依赖于上下文的并且情况并非总是如此,因此问题的一个消极方面是你可以信任第一个错误,但下一个错误可能只是第一个错误的结果,并且需要经验来实现哪个错误。

所有这些都是实现的质量(因此依赖于编译器),并且许多实现允许您确定何时停止,因此由用户和传递给编译器的标志决定。编译器正在获得更好的报告错误并从中恢复,因此您可以在此处获得改进。为了进一步改进,&gt; C ++ 14(C ++ 17?稍后?)将添加旨在改进错误消息的概念。

汇总:

  • 这是实现的质量,可以使用编译器标志进行控制
  • 并非每个人都想要你想要的东西,有些人希望在每个编译器传递中检测到多个错误
  • 未来将出现更好的错误消息(概念,编译器改进)

答案 1 :(得分:4)

我同意DavidRodríguez - dribeas并且在编译器编写者的辩护中考虑这个例子:

#include <type_traits>

class A {};

// I want the nice error message below in several functions.
// Instead of repeating myself, let's put it in a function.
template <typename U>
void check() {
    static_assert(std::is_convertible<U*, const volatile A*>::value,
        "U doesn't derive publicly from A "
        "(did you forget to include it's header file?)");
}

template <typename U>
void f(U* u) {
    // check legality (with a nice error message)
    check<U>();
    // before trying a failing initialization:
    A* p = u;
}

class B; // I forget to include "B.h"

int main() {
    B* b = nullptr;
    f(b);
}

f<B>的实例化启动时,编译器(或编译器编写器)可能会想:“嗯......我需要实例化check<U>,人们总是抱怨编译模板太慢了。所以我会继续前进,也许下面有一些错误,我不需要实例化check。“

我相信上面的推理是有道理的。 (请注意,我不是编译器写入器,所以我只是在这里猜测。)

GCC 4.8和VS2010都在继续编译f<B>,推迟check<B>的实例化以供日后使用。然后他们找到失败的初始化并提供自己的错误消息。 VS2010立即停止,我没有收到我的错误消息!海湾合作委员会继续前进并产生我想要的信息(但仅限于它自己的信号)。

元编程对程序员和编译器来说都很棘手。 static_assert帮助很多,但它不是灵丹妙药。