【问题标题】:Why isn't constexpr implied when applicable?为什么在适用时不隐含 constexpr?
【发布时间】:2013-09-18 16:02:01
【问题描述】:

这些可能应该在不同的问题中,但它们是相关的......

  1. 为什么我们需要写constexpr?鉴于一组限制,编译器不能评估代码以查看它是否满足 constexpr 要求,如果满足则将其视为 constexpr? 作为纯粹的文档关键字,我不确定它是否成立up 因为我想不出我(别人的 constexpr 函数的用户)应该真正关心它是否是运行时的情况。

    这是我的逻辑:如果它是一个昂贵的函数,我认为作为一个好的做法我应该这样对待它,无论我是否给它编译时常量输入。这可能意味着在加载时间调用它并保存结果,而不是在执行的关键点调用它。原因是因为constexpr 实际上并没有向我保证它不会在运行时首先执行——所以也许应该使用新的/不同的机制来做到这一点。

  2. constexpr restrictions 似乎排除了许多(如果不是大多数) 函数在编译时评估,这在逻辑上是可能的。我读过这至少是部分(或全部?)以防止无限循环和挂起编译器。但是,如果这是 的原因,它合法吗?

编译器是否应该能够计算,对于使用给定输入的任何给定constexpr 函数,它是否无限循环?这不是解决halting problem的任何输入。 constexpr 函数的输入是编译时间常数和有限的,因此编译器只需检查有限输入集的无限循环:实际使用的输入。 如果你写了编译时无限循环,这应该是一个常规的编译错误。

【问题讨论】:

  • 2) 由于编译器算法事先不知道您的输入和您的 constexpr 函数,因此这是编译器的停机问题。正是
  • 停止问题正是:给定一个程序和该程序的输入确定该输入的程序执行是否停止。
  • "如果你写了一个编译时无限循环,这应该是一个常规的编译错误。"这就是问题所在,除了运行它之外,没有通用算法来确定它是否是一个无限循环。如果是这样的话,那需要无限的时间......
  • 1) 编译器在编译时计算不同函数的能力可能因编译器而异。 constexpr 要求是一些共同点:程序员声明 constexpr 会强制编译器检查标准要求,即使该表达式实际上并未在代码中使用(例如,库编写者可能会专门为库用户提供该表达式)

标签: c++ c++11 constexpr


【解决方案1】:

我问了一个非常相似的问题,Why do we need to mark functions as constexpr?

当I pressedRichard Smith,Clang 的作者,他解释说:

constexpr 关键字确实有用。

它会影响何时实例化函数模板特化(如果在未评估的上下文中调用 constexpr 函数模板特化,则可能需要实例化它们;对于非 constexpr 函数而言,情况并非如此,因为对 one 的调用永远不会成为一部分常量表达式)。如果我们删除了关键字的含义,我们必须尽早实例化更多的特化,以防调用碰巧是一个常量表达式。

它通过限制实现在翻译期间尝试评估的函数调用集来减少编译时间。 (这对于需要实现来尝试常量表达式求值的上下文很重要,但如果这种求值失败也不是错误——尤其是静态存储持续时间的对象的初始化程序。)

一开始这一切似乎并不令人信服,但如果你仔细研究细节,没有constexpr,事情就会解开。一个函数在被 ODR 使用之前不需要被实例化,这本质上意味着在运行时使用。 constexpr 函数的特别之处在于它们可以违反此规则并且无论如何都需要实例化。

函数实例化是一个递归过程。实例化一个函数会导致它使用的函数和类的实例化,而不管任何特定调用的参数。

如果在实例化此依赖树时出现问题(可能会付出巨大的代价),则很难接受错误。此外,类模板实例化可能会产生运行时副作用。

给定函数签名中依赖于参数的编译时函数调用,重载解析可能会导致函数定义的实例化,这仅仅是对重载集中的那些定义的辅助,包括甚至没有被调用的函数。这种实例化可能会产生副作用,包括格式错误和运行时行为。

可以肯定,这是一个极端情况,但如果您不要求人们选择加入 constexpr 函数,则可能会发生坏事。

对于constexpr 对象,某些类型可以生成核心常量表达式,这些表达式可以在常量表达式上下文中使用,而无需声明constexpr。但是您并不真的希望编译器在编译时尝试评估每个表达式。这就是不断传播的目的。另一方面,在编译时记录何时需要发生似乎非常重要。

【讨论】:

    【解决方案2】:

    [注意,我完全改变了我的答案]

    回答你的第二个问题,这里的编译器有两种情况:

    1. 编译器必须能够处理任何任意constexpr函数。在这种情况下,您仍然会遇到停止问题,因为输入集是 constexpr 函数和对它们的调用的所有组合。

    2. 编译器可以处理一组有限的constexpr 函数。在这种情况下,编译器实际上可以确定某些程序是否会导致无限循环,而其他程序将无法编译(因为它们不在有效输入集中)。

    因此,可能存在限制,以便在合理的编译器工作量下满足案例 2。

    【讨论】:

    • 您可以在 constexpr 函数中使用非 constexpr 参数,只是不会在编译时执行。
    【解决方案3】:

    这个决定背后既有技术原因,也有意识形态原因。

    • 默认情况下,我们并不总是想要 constexpr 我们自己 - 它可能需要 编译时间太多。这是第一个。想象一下你实现了 isPrime 函数,你有 100 次调用 big constexpr 传入的值。我认为您(在大多数情况下)不想 编译器将其编译了几分钟,因为它 决定您自己在编译时需要这些值。但如果 正是这种情况 - 手动指定 constexpr 修饰符。这增加了下一点:

    • 向后兼容性 - 假设每个可能将此程序转换为 C++11 的 C++98 程序作者都想要constexpr,这是不明智的。

    • 第二点是判断函数是否可以constexpr 会自己花费编译时间。如果它试图对每一个可能的功能都这样做,那将需要一些额外的时间开销。甚至更多,通常是编译器 无法决定给定的函数是否可以是 constexpr ,所以 你的第一个假设是不正确的。

    【讨论】:

    • 关于您的第一点,我已经考虑了一段时间,但我认为我不同意。我总是希望 constexpr 可以完成,但我希望编译器足够聪明,可以在编译之间缓存结果,并且不必重新计算 constexpr 函数,除非它们或它们的输入发生变化。
    • @Dave,好的,我可以相信你,但其他人并非如此。当然,在某些情况下,默认情况下您不希望这样做。最后一点:向后兼容性——假设每个将 C++98 程序移植到 C++11 的人都默认需要 constexpr 是不明智的。
    • 我认为我的问题应该是:1) constexpr 只是 一个优化提示和文档关键字吗? 2) 这在语言中是史无前例的吗? 3) purely 用于文档和/或提示编译器进行优化的关键字是否属于 C++?
    • 作为一种优化,编译器应该只包含一个标志来打开和关闭它。那些不想要编译成本的人可以将其关闭,就像他们可以在编译器级别关闭其他优化一样。作为一个文档关键字,我只能看到它有助于了解您可以在需要编译时间常数的地方使用什么(如切换案例等)。即使这样,一个不错的 IDE 也可以在没有关键字的情况下告诉您。
    • constexpr 不是一个提示 - 它是一个强有力的保证,一个值将在编译时计算,这是它的主要和唯一目的。但是这种保证不仅可以用于优化,还可以用于语言需要 constexpr 值的情况(例如,数组大小:int arr[g()] 仅当 g 为 constexpr 函数时才有效)。
    猜你喜欢
    • 2012-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-11
    • 1970-01-01
    • 2016-07-03
    • 2016-08-30
    • 2014-02-27
    相关资源
    最近更新 更多