27

C++ 的问题之一是我们从大量使用模板和模板元编程的代码中得到可怕的错误消息。这些概念旨在解决这个问题,但不幸的是它们不会出现在下一个标准中。

我想知道,这个问题对于所有支持泛型编程的语言来说都很常见吗?还是 C++ 模板有问题?

不幸的是,我不知道任何其他支持泛型编程的语言(Java 和 C# 泛型过于简化,不如 C++ 模板强大)。

所以我问你们:D、Ada、Eiffel 模板(泛型)是否也会产生如此丑陋的错误消息?是否有可能拥有具有强大通用编程范式但没有丑陋错误消息的语言?如果是的话,这些语言是如何解决这个问题的?

编辑:对于downvoters。我真的很喜欢 C++ 和模板。我并不是说模板不好。实际上,我是通用编程和模板元编程的忠实粉丝。我只是在问为什么我会从编译器那里收到如此丑陋的错误消息。

4

6 回答 6

18

总的来说,我发现泛型的 Ada 编译器错误消息实际上并不比任何其他 Ada 编译器错误消息更难阅读。

另一方面,C++ 模板错误消息因错误小说而臭名昭著。我认为主要区别在于 C++ 进行模板实例化的方式。问题是,C++ 模板比 Ada 泛型灵活得多。它非常灵活,几乎就像一个宏预处理器。Boost 中的聪明人已经使用它来实现诸如 lambdas 甚至其他语言之类的东西。

由于这种灵活性,每次第一次遇到模板参数的特定排列时,基本上都必须重新编译整个模板层次结构。因此,解决到 API 下几层不兼容的问题最终会被提交给糟糕的 API 客户端进行解密。

在 Ada 中,泛型实际上是强类型的,并向客户端提供完整的信息隐藏,就像普通的包和子例程一样。因此,如果您确实收到一条错误消息,它通常只是引用您尝试实例化的一个泛型,而不是用于实现它的整个层次结构。

所以是的,C++ 模板错误消息比 Ada 的要糟糕得多。

现在调试完全是另一回事了......

于 2011-04-04T15:52:07.840 回答
17

从本质上讲,问题在于无论在什么情况下,错误恢复都很困难。

当您考虑到 C 和 C++ 可怕的语法时,您只会想知道错误消息并没有比这更糟糕!我担心 C 语法是由对语法的基本属性一无所知的人设计的,其中一个是对上下文的依赖越少越好,另一个是你应该努力做到它尽可能明确。

让我们说明一个常见错误:忘记分号。

struct CType {
  int a;
  char b;
}
foo
bar() { /**/ }

好的,这是错误的,缺少的分号应该去哪里?不幸的是,它是模棱两可的,它可以在之前或之后出现,foo因为:

  • C 认为在定义一个变量之后大步声明一个变量是正常的struct
  • C 认为不为函数指定返回类型是正常的(在这种情况下,它默认为int

如果我们推理,我们可以看到:

  • 如果foo命名一个类型,那么它属于函数声明
  • 如果不是,它可能表示一个变量......当然,除非我们打错字并且它是要写fool的,它恰好是一个类型:/

如您所见,错误恢复是非常困难的,因为我们需要推断作者的意思,而语法远不能接受。但这并非不可能,而且大多数错误确实可以或多或少正确地诊断出来,甚至可以从中恢复……只是需要相当大的努力。

似乎从事工作的人gcc对生成快速代码(我的意思是快速,搜索 gcc 4.6 上的最新基准)和添加有趣的功能(gcc 已经实现了大多数 - 如果不是全部 - 的 C++0x)比生产更感兴趣易于阅读的错误信息。你能怪他们吗?我不能。

幸运的是,有些人认为准确的错误报告和良好的错误恢复是一个非常有价值的目标,其中一些人已经在 CLang 上工作了很长时间,而且他们还在继续这样做。

一些不错的功能,在我的脑海中:

  • 简洁但完整的错误消息,其中包括源范围以准确揭示错误的来源
  • 当它的意思很明显时,Fix-It会注明
  • 在这种情况下,编译器会解析文件的其余部分,就好像修复程序已经存在一样,而不是在乱码行上喷出一行
  • (最近)避免包含注释的包含堆栈,以减少内容
  • (最近)只尝试公开开发人员实际编写的模板参数类型,并保留 typedefs(因此谈论std::vector<Name>而不是std::vector<std::basic_string<char, std::allocator<char>>, std::allocator<std::basic_string<char, std::allocator<char>> >让一切变得不同)
  • (最近)在丢失的情况下正确恢复,以防template在另一个模板方法中调用模板方法时丢失

但每一个都需要几个小时几天的工作。

他们当然不是免费的。

现在,概念应该(通常)让我们的生活更轻松。但它们大多未经测试,因此认为最好将它们从草案中删除。我必须说我为此感到高兴。鉴于 C++ 的相对惯性,最好不要包含尚未彻底修改的功能,而且概念图并没有让我真正兴奋。他们似乎也没有让 Bjarne 或 Herb 激动,因为他们说他们将从头开始重新考虑概念,以制定下一个标准。

于 2011-04-03T15:16:40.480 回答
11

泛型编程一文概述了多种语言中泛型的许多优缺点,尤其是 Ada。尽管缺少模板特化,但所有Ada 泛型 实例都“等同于实例声明……紧跟在实例主体之后”。实际上,错误消息往往发生在编译时,它们通常代表熟悉的违反类型安全的行为。

于 2011-04-03T20:27:05.887 回答
9

D 有两个功能可以提高模板错误消息的质量:约束和static assert.

// Use constraints to only allow a function to operate on random access 
// ranges as defined in std.range.  If something that doesn't satisfy this
// is passed, the compiler will error before even trying to instantiate
// fun().
void fun(R)(R range) if(isRandomAccessRange!(R)) {
    // Do stuff.
}


// Use static assert to check a high level invariant.  If 
// the predicate is false, the error message will be 
// printed and compilation will stop before a screen 
// worth of more confusing errors are encountered.
// This function takes any number of ranges to merge sort
// and the same number of temporary buffers to merge into.
void mergeSort(R...)(R ranges) {
    static assert(R.length % 2 == 0, 
        "Must have equal number of ranges to be sorted and temporary buffers.");

    static assert(allSatisfy!(isRandomAccessRange, R), 
        "All arguments to mergeSort must be random access ranges.");

    // Implementation
}
于 2011-04-03T14:26:55.610 回答
5

Eiffel 拥有所有错误消息中最好的,因为它拥有所有模板系统中最好的。它完全集成到语言中并且运行良好,因为它是唯一在参数中使用协变的语言。

因此,它不仅仅是一个简单的编译器复制和粘贴。不幸的是,用几行来解释差异是不可能的。去看看 EiffelStudio。

于 2011-04-20T00:52:29.837 回答
2

有一些努力可以改善错误消息。例如, Clang非常重视生成更易于阅读的编译器错误消息。我只使用了很短的时间,但与 GCC 的等效错误相比,到目前为止我对它的体验是相当积极的。

于 2011-04-03T11:56:22.657 回答