为什么C ++模板错误消息如此恐怖?


28

C ++模板臭名昭著,因为它会生成冗长且无法读取的错误消息。我对C ++中的模板错误消息为何如此糟糕有一个大致的了解。本质上,问题在于直到编译器遇到模板中某种类型不支持的语法时才触发错误。例如:

template <class T>
void dosomething(T& x) { x += 5; }

如果T不支持该+=运算符,则编译器将生成一条错误消息。而且,如果这种情况发生在某处库的深处,则错误消息可能长达数千行。

但是C ++模板本质上只是一种编译时鸭子类型的机制。C ++模板错误从概念上讲非常类似于动态语言(例如Python)中可能发生的运行时类型错误。例如,考虑以下Python代码:

def dosomething(x):
   x.foo()

在这里,如果x没有foo()方法,Python解释器将引发异常,并显示堆栈跟踪以及非常清楚的错误消息,指出问题所在。即使直到解释器深入某个库函数内部才触发错误,运行时错误消息也仍然不像典型的C ++编译器所发出的不可读呕吐那样糟糕。那么,为什么C ++编译器无法更清楚地指出出了什么问题?为什么某些C ++模板错误消息从字面上导致我的控制台窗口滚动5秒钟以上?


6
一些编译器具有可怕的错误消息,但其他确实很好(clang++眨眼眨眼)。
本杰明·班尼尔

2
因此,您希望您的程序在运行时失败,由客户交付而不是在编译时失败?
P Shved

13
@Pavel,不。这个问题与运行时与编译时错误检查的优缺点无关。
Channel72 2011年

1
作为大型C ++模板错误的示例,FWIW:codegolf.stackexchange.com/a/10470/7174
kebs

Answers:


28

模板错误消息可能是臭名昭著的,但绝不总是很长且不可读。在这种情况下,整个错误消息(来自gcc)为:

test.cpp: In function void dosomething(T&) [with T = X]’:
test.cpp:11:   instantiated from here
test.cpp:6: error: no match for operator+=’ in x += 5

就像在您的Python示例中一样,您将获得模板实例化点的“堆栈跟踪”,以及指示问题的清晰错误消息。

有时,由于各种原因,与模板相关的错误消息可能会变得更长一些:

  • “堆栈跟踪”可能更深
  • 类型名称可能会更长,因为模板将以其他模板实例化为模板进行实例化,并以其所有名称空间限定符进行显示
  • 当重载解析失败时,错误消息可能包含候选重载列表(每个重载可能包含一些非常长的类型名称)
  • 如果在许多地方实例化了无效的模板,则可能会多次报告同一错误。

与Python的主要区别是静态类型系统,导致在错误消息中包括(有时很长)类型名称的必要性。如果没有他们,这有时会是非常难以诊断为什么重载决议失败。有了它们,您的挑战不再是猜测问题的出处,而是解读告诉您问题出在哪里的象形文字。

同样,在运行时检查意味着程序将在遇到的第一个错误时停止,仅显示一条消息。编译器可能会显示它遇到的所有错误,直到放弃为止。至少在C ++中,它不应在文件中的第一个错误时停止,因为这可能是后续错误的结果。


4
您能否举一个例子说明错误是由以后的错误引起的?
罗斯兰

12

一些明显的原因包括:

  1. 历史。当gcc,MSVC等是新增功能时,它们无法使用大量额外空间来存储数据以产生更好的错误消息。内存不足,以至于他们无法做到。
  2. 多年来,消费者一直忽略错误消息的质量,因此供应商大多也这样做。
  3. 使用某些代码,编译器可以稍后在代码中重新同步并诊断实际错误。模板中的错误非常严重,以至于超过第一个错误几乎总是无用的。
  4. 模板的一般灵活性使您很难猜测可能在你的代码中有一个错误的意思。
  5. 在模板内部,名称的含义不仅取决于模板的上下文,取决于实例化的上下文,依赖于参数的查找可以添加更多可能性。
  6. 函数重载可以为特定的函数调用提供很多候选对象可能引用的,并且某些歧义性时,某些编译器(例如gcc)会尽职尽责地列出它们。
  7. 许多编码器从不考虑使用普通参数而不确保传递的值符合要求,甚至根本不尝试检查模板参数(我不得不承认,我倾向于这样做)。

这远非详尽无遗,但您可以理解。即使不容易,大多数也可以治愈。多年来,我一直在告诉人们定期使用Comeau C ++。一次我可能已经从一条错误消息中节省了足够的钱来支付编译器费用。现在Clang达到了同一个点(而且价格甚至更低)。

我将以听起来像是在开玩笑的一般观察结束,但实际上并非如此。大多数时候,老实说,编译器的真正工作将源代码转换为错误消息。现在是时候让供应商集中精力更好地完成这项工作了-尽管我会公开承认在编写编译器时,我倾向于将其视为次要的(最好),在某些情况下几乎忽略了它。完全。


9

一个简单的答案是,因为Python是按这种方式设计的,而与模板相关的许多东西都是偶然产生的。例如,它从未打算成为图灵完备的系统。而且,如果您无法故意计划和推理系统正常运行时发生的情况,那么为什么任何人都应该期望仔细,周到地计划发生问题时的运行情况呢?

而且,正如您所指出的那样,Python解释器可以通过显示堆栈跟踪信息使您更轻松,因为它正在解释Py​​thon代码。如果C ++编译器遇到模板错误并为您提供了堆栈跟踪,那将与“模板呕吐”一样无用,不是吗?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.