【问题标题】:Can adding 'constexpr' change the behaviour?添加“constexpr”可以改变行为吗?
【发布时间】:2015-11-30 13:20:43
【问题描述】:

给定两个程序,其中源代码的唯一区别是存在或不存在一个constexpr,程序的含义是否可能改变?

换句话说,如果有一个编译器选项要求编译器在可能的情况下非常努力地推断constexpr,它会破坏现有的标准代码和/或以不好的方式改变其含义吗?

想象一下处理一个代码库,其中原始开发人员忘记在可能的地方包含constexpr,可能是在 C++11 之前编写的代码。如果编译器能够推断出constexpr 来帮助您继续工作,那就太好了。当然,也许它还应该在每次执行此推断时发出警告,鼓励您稍后显式添加constexpr。但它仍然很有用。我担心它可能会破坏东西?

到目前为止,我唯一能想到的是 constexpr 函数隐含地是 inline 并且在某些情况下添加 inline 会以不好的方式改变事情;例如,如果您违反了单一定义规则。

【问题讨论】:

  • 好吧,例如,如果编译器供应商选择将标准中未标记的函数标记为 constexpr,这可能会通过 SFINAE 导致不同的行为,这就是最终不允许这样做的原因,请参阅 Is it a conforming compiler extension to treat non-constexpr standard library functions as constexpr?跨度>
  • 感谢@ShafikYaghmour。我用 SFINAE 做了一些实验,试图找到分歧,但我做不到。我想我的例子太简单了:)
  • 我有一个an example here where SFINAE breaks,原因是对常量表达式中未定义行为的不同处理。我仍然没有答案,这是否被认为是符合要求的。不完全相同,但我们可以看到不同的实现如何破坏 SFINAE。
  • 一个推断constexpr的编译器应该,根据定义,只在不改变行为的情况下这样做。
  • @Jens 是的,但他们必须遵守 as-if 规则。

标签: c++ c++11 constexpr


【解决方案1】:

给定两个程序,其中源代码的唯一区别是 一个 constexpr 的存在或不存在,它的含义是否可能 程序有什么变化?

是的,这至少对于 constexpr 函数是正确的。这就是不允许实现选择哪些标准函数标记为 constexpr 的原因,主要问题是用户可能通过 SFINAE 观察到不同的行为。这记录在LWG issue 2013: Do library implementers have the freedom to add constexpr? 中,上面写着(emphasis mine):

在提交给全体委员会投票时表达了一些担忧 到 WP 状态,此问题已在没有足够的情况下得到解决 考虑不同库实现的后果, 因为用户可以使用 SFINAE 从其他相同的代码中观察不同的行为。问题移回审核状态,并将 在波特兰与更大的小组再次讨论。波特兰注意事项: John Spicer 已同意在任何此类事件中代表 Core 的担忧 LWG 内部讨论。

【讨论】:

  • @AaronMcDaid 我实际上是在尝试制作一个实时示例,自从我查看该代码以来已经有一段时间了,我想确保我的细节是正确的。
  • constexpr 函数在特定的情况下是正确的。除此之外,constexpr 也有其他用途,所以我会说它部分正确。
  • (我刚刚删除了我之前对此答案所做的评论,因为我还无法使用它来创建一个示例,其中由于constexpr 的存在而导致行为发生变化)
  • @AaronMcDaid 这里是an example,clang 和 gcc 之间的行为不同,其中 cos 在 gcc 中标记为 constexpr 而不是 clang。我在未定义的行为示例中遇到了麻烦。我从来没有保存过我的班次示例,复制它很痛苦。
  • 请注意,如果你有一个很好的例子,那么将它包含在你的答案中,以后复制永远不会像你想象的那样微不足道。
【解决方案2】:

有一个简单的技巧:

template<int n>struct i{};
int foo(int){return 0;}
constexpr int foo(char){return 'a';}

template<class T=int, T x=1,i<foo(x)>* =nullptr>
bool bar(){return true;}
template<class T=int, T x=1,class...Ts>
bool bar(Ts...){return false;}

如果 int foo(int) 是 constexpr,则默认选择不同的 bar 重载。

运行不同的代码,任何行为都可能发生变化。

live example(只需更改 #define X 被注释掉的位置)。


示例设计:

char 重载可防止上述代码格式错误,无需诊断,因为所有模板都必须具有有效的特化。 foo&lt;char&gt; 提供。实际上,它的存在不是必需的:ADL 可以从很远的地方找到一个foo,在一个some_type* 上重载,然后将some_type* 传递为T。这意味着没有编译单元可以证明代码格式错误。

Ts... 使得 bar 重载不太受欢迎。因此,如果第一个匹配,则没有歧义。只有当第一个重载失败(由于 foo(x) 不是 constexpr 引起的 SFINAE)时,才会调用第二个重载(或者,如果有人向它传递了参数)。

【讨论】:

  • 完美。我一直在努力制作能够在 clang 和 g++ 中展示与 constexpr 相关的行为的东西。但这对 clang 3.5.0 和 g++ 5.2.0 都有效。
  • .. 我想这表明可以在编译时检测给定函数是否为constexpr。虽然这看起来很酷,但这意味着推断constexpr 会很危险,因为它会改变这些测试的结果。最后,我不是很喜欢这个,现在我明白了。我认为这应该给出一个错误——如果最好的重载不是constexpr,那么如果在需要常量表达式的上下文中调用它应该会有一个错误。 (以我的拙见,在最后几分钟才形成)
  • @AaronMcDaid 错误在直接上下文中,因此我们得到替换失败而不是错误。 SFINAE 意味着几乎任何“签名”更改(甚至存在!)都可能导致 C++ 中不同的编译时和运行时行为,并且几乎对任何接口的扩展都无法完全符合。但是,发生这种情况的情况是(在我见过的所有情况下,包括上面的情况)病态
  • 我刚刚注意到 foo(char) 不需要是 constexpr 才能使此示例正常工作。两个编译器仍然显示取决于 foo(int) 是否为 constexpr 的行为。所以这改变了我对答案如何工作的(有限的)理解。 (待续……)
  • 我只是使用 SFINAE 来确定 i&lt;foo(x)&gt; 是否是直接上下文中的有效表达式。 char 重载仅适用于标准的模糊技术需求(所有模板都能够使用某些参数集进行实例化)。
猜你喜欢
  • 2020-03-16
  • 1970-01-01
  • 2020-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多