将非constexpr标准库函数视为constexpr,这是符合编译器扩展的吗?

44

gcc编译以下代码时没有警告:

#include <cmath>

struct foo {
  static constexpr double a = std::cos(3.);
  static constexpr double c = std::exp(3.);
  static constexpr double d = std::log(3.);
  static constexpr double e1 = std::asin(1.);
  static constexpr double h = std::sqrt(.1);
  static constexpr double p = std::pow(1.3,-0.75);
};

int main()
{
}

以上使用的标准库函数均不是constexpr函数,根据C++11标准草案C++14标准草案7.1.5[dcl.constexpr]规定,我们可以在需要常量表达式的地方使用它们:

[...]如果它通过构造函数调用进行初始化,则该调用应为常量表达式(5.19)。否则,或者如果在引用声明中使用了constexpr说明符,则出现在其初始化器中的每个完整表达式都应为常量表达式.[...]

即使使用-std=c++14 -pedantic-std=c++11 -pedantic,也不会产生警告(请参见实时演示)。使用-fno-builtin会产生错误(请参见实时演示),这表明这些标准库函数的内置版本被视为constexpr
虽然我尝试了多种标志组合,但clang不允许该代码。
因此,这是一个gcc扩展,将至少一些内置函数视为constexpr函数,即使标准并未明确要求。在严格的符合模式下,我至少希望收到警告,这是一个符合性扩展吗?
1个回答

44

TL;DR

C++14明确不允许这种情况,尽管在2011年似乎会明确允许这种情况。不清楚对于C++11来说是否属于as-if rule,我认为它不属于,因为它改变了可观察的行为,但这一点在下面引用的问题中没有澄清。

详细信息

这个问题的答案随着LWG issue 2013的演变而改变,其中开头是:

假设某个特定函数在标准中没有被标记为constexpr,但在某个特定的实现中,可以在constexpr约束条件下编写它。如果实现者将这样的函数标记为constexpr,那么这是违反标准还是符合扩展?

在C++11中,不清楚“as-if规则”是否允许这样做,但是原始提案一旦被接受,就会明确允许它。我们可以从我引用的gcc错误报告中看到,这是gcc团队所做出的假设。
2012年,允许此操作的共识发生了变化,提案也发生了改变,在C++14中,这是一个不符合规范的扩展。这反映在草案C++14标准的第17.6.5.6节 [constexpr.functions] 中,其中说:
“[...]除非明确要求,否则实现不得将任何标准库函数签名声明为constexpr。[..]”
虽然严格阅读似乎留下了一些余地,使内置函数隐式地像constexpr一样处理,但从以下问题中的引用中,我们可以看出意图是防止实现中的分歧,因为相同的代码在使用SFINAE时可能会产生不同的行为(重点是我的):
当提交给全委员会进行投票以获得WP状态时,有些人对此表示担忧,认为这个问题在解决时没有充分考虑到不同的库实现所带来的后果,因为用户可能使用SFINAE从本质上相同的代码中观察到不同的行为。
我们可以从gcc bug报告中看到[C++0x] sinh vs asinh vs constexpr,团队依赖于早期提出的LWG 2013的解决方案。该解决方案表示:
此外,如果函数定义满足必要的约束条件,则实现可以声明任何函数为constexpr。
当决定是否允许在严格符合模式下进行数学函数的更改时,就会使用该解决方案。
据我所知,如果在严格符合模式下收到警告,即使用-std=c++11 -pedantic,或者在此模式下禁用它,那么这将成为符合规范的做法。

请注意,我在错误报告中添加了一条评论,解释自最初处理此问题以来分辨率已更改。

Jonathan Wakely在另一个问题中指出了一次更近期的讨论,似乎gcc错误报告将被重新打开以解决这个符合性问题。

关于内部函数

编译器内部函数不在标准范围内,因此据我所知,它们应该免除此规则,因此可以使用:

static constexpr double a = __builtin_cos(3.);

应该被允许。这个问题在错误报告中提出,Daniel Krügler的意见是:

[...]库函数和其他内置函数可能可以被视为例外,因为它们不需要遵循正常语言规则来“解释”。


@Walter,我添加了一个“简而言之”部分。如果还有其他需要澄清的地方,请告诉我。 - Shafik Yaghmour
内置函数怎么样? - Columbo
我认为谈论编译器内部函数并不有益。所有以__开头的名称都保留给实现,实现定义它们的语义。对于特定于实现的函数的语义,无论它们是否是内部函数,完全可以由实现者自行决定将其定义为constexpr - Ben Voigt
而且“仿佛”规则肯定不允许这样做。 “仿佛”只允许具有相同可观察行为的不同实现,而SFINAE更改是非常明显的差异。 - Ben Voigt
@BenVoigt,在gcc错误报告中提到了内部函数的主题,以及在此处的评论中提到了它,因此即使答案似乎很明显,提到它似乎也是相关的。我同意您关于as-if规则的看法,我应该让它更明显,现在已经更新了。 - Shafik Yaghmour
@BenVoigt:SFINAE的变化可能是可观察的变化,但似乎仍然应该有一些构造,编译器可以允许但不需要将其视为替换失败。如果有一个内在的“要求表达式按照某个特定版本的标准定义为constexpr”的规定,但其他地方本来需要constexpr的地方可以接受(或不接受)编译器可以推断出值的东西,那么这将使语言能够在更多的上下文中有用地识别更多形式的常量表达式,从而使语言更加实用。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接