为啥**不**将函数声明为`constexpr`?

Posted

技术标签:

【中文标题】为啥**不**将函数声明为`constexpr`?【英文标题】:Whyever **not** declare a function to be `constexpr`?为什么**不**将函数声明为`constexpr`? 【发布时间】:2011-07-03 23:52:43 【问题描述】:

任何只包含 return 语句的函数都可以被声明 constexpr 因此将允许在编译时进行评估,如果所有 参数是constexpr,并且只有constexpr 函数在其主体中被调用。 有什么理由不声明任何这样的功能constexpr

例子:

  constexpr int sum(int x, int y)  return x + y; 
  constexpr i = 10;
  static_assert(sum(i, 13) == 23, "sum correct");

谁能提供一个声明函数constexpr的例子 会造成什么伤害吗?


一些初步的想法:

即使没有充分的理由去声明一个函数 不是constexpr 我可以想象constexpr 关键字有一个 过渡角色:它在不需要编译时的代码中不存在 评估将允许不实现编译时的编译器 评估仍然编译该代码(但在代码上可靠地失败 这需要它们,通过使用constexpr 进行明确说明。

但我不明白的是:如果没有充分的理由 曾经声明一个函数不是 constexpr,为什么不是 every 函数 在标准库中声明 constexpr? (你不能争论 还没有完成,因为还没有足够的时间 这样做,因为为 all 做这件事是不费吹灰之力的——这与为每个单独的功能决定是否使其成为 constexpr 相反。) --- 我知道N2976 故意不需要 cstrs 用于许多标准库类型,例如 作为容器,因为这对可能的限制太大了 实施。让我们将它们排除在论点之外,只是想知道: 一旦标准库中的一个类型实际上有一个constexpr cstr,为什么不是每个对其操作的函数都声明为constexpr

在大多数情况下,您也不能争辩说您可能不喜欢声明函数 constexpr 仅仅是因为您没有设想任何编译时使用:因为如果其他人 evtl.将使用您的代码,他们可能会看到您没有看到的这种用法。 (当然,对于类型特征类型和类似的东西是允许的。)

所以我想故意不声明函数constexpr一定有充分的理由和很好的例子?

(对于“每个功能”,我总是指:满足 要求为constexpr,即定义为单个 return 语句,只接受带有 constexpr 类型的参数 cstrs 并仅调用 constexpr 函数。由于 C++14,much more is allowed in the body of such function:例如,C++14 constexpr 函数可以使用局部变量和循环,因此可以声明更广泛的函数类constexpr。)

问题Why does std::forward discard constexpr-ness? 是这个问题的一个特例。

【问题讨论】:

我的问题是:声明为 constexpr 但调用时不会导致常量表达式的函数会发生什么,应该是编译错误不是吗?那么对于那些不是特别旨在仅在编译时进行评估的函数,它们不应该被声明为 constexpr 吗? 我搜索了标准并且找不到关于如果 constexpr 函数被非常量表达式参数调用会发生什么的提示。无论如何,如果这是错误的,那么 std::forward 的情况也很清楚,如果你定义了 std::forward constexpr,那么它必须用作 constexpr 并且不能转发普通变量。 @user534498 我也找不到标准中的位置。但是唯一有意义的事情(以及 g++ 的实际作用)是在使用非 constexp 参数调用 constexpr 函数时默默地忽略 constexpr。否则,std::bitset 中的 size 之类的函数显然无法成为 constexpr @Lars:不仅如此,我还注意到,如果 输出 不是明确的constexpr,g++ 会忽略constexpr,无论输入是否是。虽然我不确定这是否是标准的意图,但这对我来说没有任何意义。例如,将constexpr 函数的返回值分配给const int 将导致该函数出现在二进制文件中并被执行,而将其分配给enum(具有相同的输入!)只是定义了一个枚举值和根本不生成代码。 【参考方案1】:

只有遵守constexpr 规则的函数才能声明为constexpr --- 没有动态转换、没有内存分配、没有调用非constexpr 函数等。

将标准库中的函数声明为constexpr 要求所有实现都遵守这些规则。

首先,这需要检查每个可以实现为constexpr的函数,这是一项漫长的工作。

其次,这对实现是一个很大的限制,并且会禁止许多调试实现。因此,只有当收益超过成本,或者要求足够严格以至于实现几乎必须遵守constexpr 规则时,它才是值得的。对每个函数进行这种评估又是一项漫长的工作。

【讨论】:

我无意批评图书馆工作组的工作,如果我的问题听起来像这样,我很抱歉。你所说的与接口有关,你是对的,标准库描述了这样一个具有许多实现的接口。所以EVT。调查 STL 以指导 constexpr 的使用对我来说并不聪明。我的问题是关于实现的。您是否同意 implementations 应尽可能为 constexpr ?或者是否有不是 constexpr 的实现示例? (对不起,evtl。我没有明白你的意思......) 制作constexpr 的东西非常受限制。这不是我会轻易做的事情,因为一旦它成为您界面的一部分,由于向后兼容性问题,很难更改它。即使对于标准库的给定实现,这也适用 --- 许多人会不知不觉地依赖他们使用的库的实现属性,所以我不希望实现除了标准要求的功能之外,还可以创建任何功能constexpr,或者没有合理的实现会违反约束。 嗨!不好意思,还是没看懂,能给个代码例子吗?如果我们在一个函数中声明 constexpr 并且在编译器评估之后这可能是也可能不是 constexpr,那么为什么不在每个可能以这种方式编译的函数中声明它(如果我们希望在编译时运行比在运行时运行更多)? 首先,constexpr 函数隐含为inline,因此如果您不想将函数定义放在头文件中,则它们不能是constexpr。其次,声明函数constexpr意味着你不能使用try块、static变量、thread_local变量或非文字类型的变量,所以不能使用std::stringstd::vector等。 如果您有一个可能是 constexprinline 函数,那么声明它没有任何不利之处,只是它可能会限制您的未来 --- 将实现更改为不成为constexpr-compatible 将是一个 ABI 中断,因为在您的代码中的某处可能会在常量表达式中使用它。【参考方案2】:

我认为您所指的是partial evaluation。您要说的是,某些程序可以分为两部分-需要运行时信息的部分,以及无需任何运行时信息即可完成的部分-理论上您可以完全评估程序的一部分在您开始运行程序之前,它不需要任何运行时信息。有一些编程语言可以做到这一点。例如,D 编程语言在编译器中内置了一个解释器,可以让您在编译时执行代码,前提是它满足某些限制。

让部分评估发挥作用存在一些主要挑战。首先,它极大地复杂化了编译器的逻辑,因为编译器需要能够模拟所有可以在编译时放入可执行程序的操作。在最坏的情况下,这需要您在编译器内部拥有一个完整的解释器,这会导致难题(编写一个好的 C++ 编译器)并使其难以完成。

我相信目前关于constexpr 的规范的原因仅仅是为了限制编译器的复杂性。它仅限于检查的情况相当简单。无需在编译器中实现循环(这可能会导致其他一系列问题,例如如果您在编译器中获得无限循环会发生什么)。它还避免了编译器可能不得不在运行时评估可能导致段错误的语句,例如跟随一个错误的指针。

要记住的另一个注意事项是某些功能有副作用,例如从cin 读取或打开网络连接。像这样的函数根本无法在编译时进行优化,因为这样做需要仅在运行时可用的知识。

总而言之,理论上没有理由不能在编译时对 C++ 程序进行部分评估。事实上,人们一直都在这样做。例如,优化编译器本质上是尽可能多地尝试这样做的程序。模板元编程是 C++ 程序员尝试在编译器中执行代码的一个实例,部分原因是模板规则形成了一种函数式语言,编译器更容易实现这种语言,因此可以使用模板做一些很棒的事情。此外,如果您考虑编译器作者时间和编程时间之间的权衡,模板元编程表明,如果您可以让程序员向后弯腰以获得他们想要的东西,您可以构建一个非常弱的语言(模板系统)并保持语言复杂简单。 (我说“弱”是指“不是特别有表现力”,而不是可计算性理论意义上的“弱”)。

希望这会有所帮助!

【讨论】:

规则似乎没有禁止副作用。 虽然是一个非常有趣的答案(部分评估是一个很大的话题,每个编译器编写者都尝试过),但似乎有点离题,因为问题更多是关于 @ 的使用987654324@(对于程序员)似乎比它的效果。 我宁愿你离开这篇文章,这很有趣。例如,我不知道 D 嵌入了一个解释器 :) 我必须承认我对 constexpr 的要求有点模糊,我需要继续努力。 @Matthieu M. 感谢您提供的信息。对我来说,关于 D 的事情也是新鲜有趣的。但是 Matthieu 是对的,你回答了这个问题:为什么 constexpr 仅限于由单个 return 语句组成的函数?虽然我的问题是:我们现在是否必须声明每个函数 constexpr 或者是否有任何情况会产生负面影响? (但请留下你的答案!) 这是一个非常有用的答案,我同意您的观点,即 TMP 提供了一种非常好的纯函数式语言。虽然,constexpr 也允许编写纯函数式代码,同时在运行时和编译时变量之间提供了一个很好的桥梁。但是,没有像 std::find 和其他一些定义 constexpr 这样的函数似乎人为地限制了可以使用 STL 编写的纯代码的种类。【参考方案3】:

如果函数有副作用,你不会想标记它constexpr。 Example

我无法从中得到任何意想不到的结果,实际上它看起来像gcc 4.5.1 just ignores constexpr

【讨论】:

在您的示例中,f 在非 constexpr 变量 g 上调用 operator++,因此不是有效的 constexpr 函数。 g++ (snapshot 2011-02-19) 正确抛出错误信息:“g is not a constant expression”。 @Lars:我多次阅读标准(草案 3225)的[dcl.constexpr] 部分,但找不到任何阻止此功能成为constexpr 的内容,因为文本“表达式 是一个潜在的常量表达式”已被删除。 “示例”链接已失效,ideone 显示“未找到解决方案” @AlexanderMalakhov:向 ideone 报告。他们的常见问题解答保证代码“永远”可用:ideone.com/faq 我不再使用该网站,因为他们删除了内容,没有警告,并且违反了他们自己的条款。 我并不是说这是你的错 :) 但也许你记得那段代码并且可以在这里重现它?

以上是关于为啥**不**将函数声明为`constexpr`?的主要内容,如果未能解决你的问题,请参考以下文章

constexpr 和不推荐使用的转换警告

C++ 编译器优化 - 为啥需要 constexpr?

将 constexpr 添加到语言后是不是将变量声明为 const 多余?

为啥 NVCC 对 constexpr 比非 constexpr 主机函数更严格?

为啥这个 constexpr 静态成员函数在调用时不被视为 constexpr?

c++ 默认构造函数 constexpr 还是正确未定义?