std::max()和std::min()不是constexpr。

37
我刚刚注意到新标准在没有constexpr的情况下定义了min(a,b)和max(a,b)。
来自25.4.7,[alg.min.max]的示例:
template<class T> const T& min(const T& a, const T& b);
template<class T> T min(initializer_list<T> t);

这不是很遗憾吗?我本来想写的。

char data[ max(sizeof(A),sizeof(B)) ];

替代

char data[ sizeof(A) > sizeof(B) ? sizeof(A) : sizeof(B) ];
char data[ MAX(sizeof(A),sizeof(B)) ]; // using a macro

为什么这些代码不能使用 constexpr

5个回答

22

在C++14中,std::min和std::max是constexpr的,这显然意味着现在没有足够的理由不将它们设置为constexpr。问题得到解决 :-)


5
我认为这应该是一条评论。 - Angelus Mortis

13

重要更新

以下分析是错误的,因为它混淆了一个重要的事情。我错过了一个重要的细节,需要完全不同的答案。

未命名的引用max将引用该操作数。

问题在于,在那一点上执行函数调用替换。如果调用替换包括对max产生的glvalue进行lvalue到rvalue转换,那么一切都没问题,因为在计算常量表达式期间读取指向非静态存储持续时间的临时对象的glvalue是可以的。但由于读取发生在函数调用替换之外,所以函数调用替换的结果是一个lvalue。规范中相应的文本说:

引用常量表达式是指定具有静态存储持续时间或函数的lvalue核心常量表达式。

但是max返回的引用产生的lvalue指定了一个未指定存储期的对象。函数调用替换需要产生一个常量表达式,而不仅仅是一个核心常量表达式。因此,max(sizeof(A), sizeof(B))不能保证有效。

需要考虑上述内容阅读以下(较旧的)文本。


我目前看不到任何理由为什么你不想在那里加上constexpr。无论如何,以下代码肯定是有用的。

template<typename T> constexpr
T const& max(T const& a, T const& b) {
  return a > b ? a : b;
}

与其他答案所写不同,我认为这是合法的。并非所有实例化的max都需要成为constexpr函数。当前的n3242说明如下:
如果constexpr函数模板或类模板成员函数的实例化模板特化不满足constexpr函数或constexpr构造函数的要求,则该特化不是constexpr函数或constexpr构造函数。
如果您调用该模板,则参数推导将产生函数模板特化。调用它将触发函数调用替换。考虑以下呼叫。
int a[max(sizeof(A), sizeof(B))];

它将首先对两个size_t prvalue进行隐式转换,将两个引用参数绑定到存储其值的临时对象。这种转换的结果是每种情况下都引用一个临时对象的glvalue(请参见4p3)。现在,函数调用替换使用这两个glvalue,并通过这些glvalue替换函数体中所有ab的出现。
return (<glval.a>) > (<glval.b>) ? (<glval.a>) : (<glval.b>);

该条件将在这些glvalues上需要lvalue到rvalue的转换,这是由5.19p2允许的。
  • 字面类型的glvalue,它引用了一个使用常量表达式初始化的非易失性临时对象
条件表达式将产生一个glvalue,指向第一个或第二个操作数。未命名的引用max将引用该操作数。在数组维度大小规范中发生的最终lvalue到rvalue转换将通过上述相同的规则有效。
请注意,initializer_list目前没有constexpr成员函数。这是一个已知的限制,并将在C++0x之后处理,很可能使这些成员成为constexpr

1
我同意你的推理,尽管我不能完全理解你所说的“rvalue”、“glvalue”的魔法。我在标准库中看到了这些定义,但我想我还需要更深入地研究一下。 - towi
2
C++委员会建议函数调用替换可以产生引用临时变量的左值。g++遵循这种方式,但是clang目前按照标准实现。 - Richard Smith

1
C++14中包括constexpr版本的std::min()std::max()表明(版本)这些函数成为constexpr没有根本障碍。当constexpr添加到C++11时,似乎没有足够的考虑。

显然,对于提供比较函数的版本,该函数本身必须是constexpr,才能使模板扩展成功。


-1

minmax只有在使用常量表达式作为参数调用它们时才是常量表达式。由于它们的用途比这更普遍,因此您无法进行声明。

这里是Wikipedia关于constexpr的说明(已加重)。我知道维基百科不是最终参考资料,但我相信在这种情况下它是正确的。

在函数上使用constexpr会对该函数的功能施加非常严格的限制。首先,函数必须具有非void返回类型。其次,函数内容必须采用以下形式:return expr。第三,expr必须是常量表达式,在参数替换后。此常量表达式只能调用其他定义为constexpr的函数,或者可以使用其他常量表达式数据变量。


7
替换完参数后,constexpr 不会使可变参数失效。 - Potatoswatter
4
我有点不同意。非常直接的实现 T max(T a, T b) { return a>b ? a : b; } 是可以被声明为 constexpr 的。这个声明与其使用是独立的。如果我将其用于 constexpr 参数,我可以获得一个 constexpr 结果。 - towi
2
Mark的回答是不正确的。至少在C++11中,minmax永远不是常量表达式,因为它们在标准库中没有声明为constexpr。但是OP问道:为什么它们没有被声明为constexpr - Quuxplusone
6
@MarkRansom,是的,我不同意你的看法。constexpr 的本质意义是“我可以是常量”,而不是“我必须始终是常量”。对于后一种情况,我们已经有了 const 变量。在 C++11 中尝试以下代码:constexpr int min(int a, int b) { return a < b ? a : b; } int main(int argc, char **argv) { char a[min(42, 43)]; static_assert(sizeof(a) == 42, "test"); printf("%d\n", min(argc, 5)); } - Quuxplusone
1
@MarkRansom http://www.cprogramming.com/c++11/c++11-compile-time-processing-with-constexpr.html - Quuxplusone
显示剩余2条评论

-3

我的猜测是,在一般情况下,operator<(T,T)也无法保证是constexpr。


1
因此,它可以被模板化,其中constexpr仅适用于本机操作数类型。 - Potatoswatter

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