C++与 D 、 Ada 和 Eiffel (带有模板的可怕错误消息)

发布于 2024-10-30 04:35:29 字数 405 浏览 5 评论 0 原文

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

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

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

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

编辑:针对投反对票的人。我真的很喜欢 C++ 和模板。我并不是说模板不好。事实上,我非常喜欢泛型编程和模板元编程。我只是问为什么我会从编译器收到如此难看的错误消息。

One of the problems of C++ are horrible error messages that we are getting from code which intensively uses templates and template metaprogramming. The concepts are designed to solve this problem, but unfortunately they will not be in the next standard.

I'm wondering, is this problem common for all languages, which are supporting generic programming? Or something is wrong with C++ templates?

Unfortunately I don't know any other language, that supports generic programming (Java and C# generics are too simplified and not as powerful as C++ templates).

So I'm asking you guys: are D,Ada,Eiffel templates (generics) producing such ugly error messages too? And Is it possible to have language with powerful generic programming paradigm, but without ugly error messages? And if yes, how these languages are solving this problem ?

Edit: for downvoters. I really love C++ and templates. I'm not saying that templates are bad. Actually I'm a big fan of generic programming and template metaprogramming. I'm just asking why I'm getting such ugly error messages from compilers.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

世界等同你 2024-11-06 04:35:30

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

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

Eiffel has the best of all error messages because it is has the best of all template systems. It is fully integrated into the language and works well because it is the only language which is using covarianz in arguments.

Therefore it is much more then a simple compiler copy and paste. Unfortunately explaining the difference in a few lines is impossible. Just go and have a look at EiffelStudio.

允世 2024-11-06 04:35:30

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

There are some efforts to improve the error messages. Clang, for example, has put quite a lot of emphasis on generating more easily readable compiler error messages. I've only been using it for a short while, but my experience of it so far has been quite positive compared to GCC's equivalent errors.

2024-11-06 04:35:29

问题的核心在于,无论在什么情况下,错误恢复都很困难。

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

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

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

好吧,这是错误的,缺少的分号应该去哪里?不幸的是,它是不明确的,它可以在 foo 之前或之后,因为:

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

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

  • 如果 foo 命名了一个类型,那么它属于函数声明
  • 如果不是,它可能表示一个变量...当然,除非我们犯了一个拼写错误,并且它本来应该写成fool,它恰好是一个类型:/

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

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

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

一些不错的功能,在我的脑海中浮现出来:

  • 简洁但完整的错误消息,其中包括源范围,以准确暴露错误源自
  • Fix-It 的注释,当它的含义很明显
  • 时编译器会解析文件的其余部分,就好像修复已经存在一样,而不是在乱码行上喷出一行又一行
  • (最近的),避免包含注释的包含堆栈,以删除多余的内容
  • (最近的),只尝试暴露开发人员实际编写的模板参数类型,并保留 typedef(因此讨论 std::vector 而不是 std::vector>、std::allocator> > 使一切变得不同)
  • (最近)在丢失的情况下正确恢复template 以防在从另一个模板方法调用模板方法时丢失该模板

但是每个模板方法都需要几个小时到几天的工作。

他们当然不是免费来的。

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

The problem, at heart, is that error recovery is difficult, whatever the context.

And when you factor in C and C++ horrid grammars, you can only wonder that error messages are not worse than that! I am afraid that the C grammar has been designed by people who didn't have a clue about the essential properties of a grammar, one of them being that the less reliance on the context the better and the other being that you should strive to make it as unambiguous as possible.

Let us illustrate a common error: forgetting a semi-colon.

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

Okay so this is wrong, where should the missing semi-colon go ? Well unfortunately it's ambiguous, it can go either before or after foo because:

  • C considers it normal to declare a variable in stride after defining a struct
  • C considers it normal not to specify a return type for a function (in which case it defaults to int)

If we reason about, we could see that:

  • if foo names a type, then it belongs to the function declaration
  • if not, it probably denotes a variable... unless of course we made a typo and it was meant to be written fool, which happens to be a type :/

As you can see, error recovery is downright difficult, because we need to infer what the writer meant, and the grammar is far from being receptive. It is not impossible though, and most errors can indeed be diagnosed more or less correctly, and even recovered from... it just takes considerable effort.

It seems that people working on gcc are more interested in producing fast code (and I mean fast, search for the latest benchmarks on gcc 4.6) and adding interesting features (gcc already implement most - if not all - of C++0x) than producing easy to read error messages. Can you blame them ? I can't.

Fortunately there are people who think that accurate error reporting and good error recovery are a very worthy goal, and some of those have been working on CLang for quite a bit, and they are continuing to do so.

Some nice features, off the top of my head:

  • Terse but complete error messages, which include the source ranges to expose exactly where the error emanated from
  • Fix-It notes when it's obvious what was meant
  • In which case the compiler parses the rest of the file as if the fix had been there already, instead of spewing lines upon lines of gibberish
  • (recent) avoid including the include stack for notes, to cut out on the cruft
  • (recent) trying only to expose the template parameter types that the developper actually wrote, and preserving typedefs (thus talking about std::vector<Name> instead of std::vector<std::basic_string<char, std::allocator<char>>, std::allocator<std::basic_string<char, std::allocator<char>> > which makes all the difference)
  • (recent) recovering correctly in case of a missing template in case it's missing in a call to a template method from within another template method

But each of those has required several hours to days of work.

They certainly didn't come for free.

Now, concepts should have (normally) made our lives easier. But they were mostly untested and so it was deemed preferable to remove them from the draft. I must say I am glad for this. Given C++ relative inertia, it's better not to include features that haven't been thoroughly revised, and the concept maps didn't really thrilled me. Neither did they thrilled Bjarne or Herb it seems, as they said that they would be rethinking Concepts from scratch for the next standard.

小镇女孩 2024-11-06 04:35:29

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

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

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

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

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

现在调试完全是一个不同的故事......

In general I found Ada compiler error messages for generics really not significantly more difficult to read than any other Ada compiler error messages.

C++ template error messages, on the other hand, are notorious for being error novels. The main difference I think is the way C++ does template instantiation. The thing is, C++ templates are much more flexible than Ada generics. It is so flexible, it is almost like a macro preprocessor. The clever folks in Boost have used this to implement things like lambdas and even whole other languages.

Because of that flexibility, the entire template hierarchy basically has to be compiled anew every time its particular permutation of template parameters is first encountered. Thus issues that resolve down to incompatibilities several layers down a API end up being presented to the poor API client to decipher.

In Ada, Generics are actually strongly typed, and provide full information hiding to the client, just like normal packages and subroutines do. So if you do get an error message, it is typically just referencing the one generic you are trying to instatiate, not the entire hierarchy used to implement it.

So yes, C++ template error messages are way worse than Ada's.

Now debugging is a different story entirely...

内心荒芜 2024-11-06 04:35:29

文章泛型编程概述了泛型的许多优点和缺点多种语言,包括特别是 Ada。尽管缺乏模板专业化,但所有 Ada 通用 < a href="http://www.adaic.org/resources/add_content/standards/05rm/html/RM-12-3.html" rel="noreferrer">实例“相当于实例声明…紧接着是实例主体”。实际上,错误消息往往发生在编译时,它们通常代表常见的类型安全违规行为。

The article Generic Programming outlines many of the pros and cons of generics in several languages, including Ada in particular. Although lacking template specialization, all Ada generic instances are "equivalent to the instance declaration…immediately followed by the instance body". As a practical matter, error messages tend to occur at compile-time, and they typically represent familiar violations of type-safety.

同展鸳鸯锦 2024-11-06 04:35:29

D 有两个功能可以提高模板错误消息的质量:约束和静态断言。

// 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
}

D has two features to improve the quality of template error messages: Constraints and 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
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文