std::max() and std::min() not constexpr
Asked Answered
D

5

37

I just noticed that the new standard defines min(a,b) and max(a,b) without constexpr.

Examples from 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);

Isn't this a pity? I would have liked to write

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

instead of

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

Any reason why those can not be constexpr?

Duelist answered 9/4, 2011 at 12:54 Comment(0)
E
13

Critical Update

The below analysis is wrong, because it confuses one important thing. The following statement I did missed one important detail, which requires an entirely different answer.

The unnamed reference max returns will refer to that operand.

The problem here is that function invocation substitution is done at that point. If the invocation susbstitution would include the lvalue to rvalue conversion on that glvalue that max yields, everything would be fine, because reading from a glvalue that refers to a temporary not of static storage duration is fine during computation of the constant expression. But since the read happens outside of function invocation substitution, the result of function invocation substitution is an lvalue. The respective text of the spec says

A reference constant expression is an lvalue core constant expression that designates an object with static storage duration or a function.

But the reference that max returns yields an lvalue that designates an object of unspecified storage duration. Function invocation substitution is required to yield a constant expression, not merely a core constant expression. So max(sizeof(A), sizeof(B)) is not guaranteed to work.

The following (older) text needs to be read taking the above into account.


I can't see any reason at the moment why you wouldn't want to stick a constexpr there. Anyway, the following code definitely is useful

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

Contrary to what other answers write, I think this is legal. Not all instantiations of max are required to be constexpr functions. The current n3242 says

If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is not a constexpr function or constexpr constructor.

If you call the template, argument deduction will yield a function template specialization. Calling it will trigger function invocation substitution. Consider the following call

int a[max(sizeof(A), sizeof(B))];

It will first do an implicit conversion of the two size_t prvalues to the two reference parameters, binding both references to temporary objects storing their value. The result of this conversion is a glvalue for each case that refers to a temporary object (see 4p3). Now function invocation substitution takes those two glvalues and substitutes all occurences of a and b in the function body by those glvalues

return (<glval.a>) > (<glval.b>) ? (<glval.a>) : (<glval.b>);

The condition will require lvalue to rvalue conversions on these glvalues, which are allowed by 5.19p2

  • a glvalue of literal type that refers to a non-volatile temporary object initialized with a constant expression

The conditional expression will yield a glvalue to either the first or second operand. The unnamed reference max returns will refer to that operand. And the final lvalue to rvalue conversion happening in the array dimension size specification will be valid by the same rule quoted above.


Note that initializer_list currently doesn't have constexpr member functions. This is a known limitation and will be handled post-C++0x, most likely making those members constexpr.

Evidentiary answered 10/4, 2011 at 15:1 Comment(2)
I agree with your reasoning, although I can not quite follow your "rvalue", "glvalue" magic. I saw those definitions in the std, but have to dig in there more deeply, I guess.Duelist
The C++ committee have suggested that it was intended to be possible for function invocation substitution to produce an lvalue referring to a temporary. g++ behaves that way, but clang currently implements the standard as written.Canopus
C
22

std::min and std::max are constexpr in C++14, which obviously means there isn't a good reason (these days) not to have them constexpr. Problem solved :-)

Carillo answered 5/3, 2016 at 12:59 Comment(1)
This should have been comment I thinkNarcosis
E
13

Critical Update

The below analysis is wrong, because it confuses one important thing. The following statement I did missed one important detail, which requires an entirely different answer.

The unnamed reference max returns will refer to that operand.

The problem here is that function invocation substitution is done at that point. If the invocation susbstitution would include the lvalue to rvalue conversion on that glvalue that max yields, everything would be fine, because reading from a glvalue that refers to a temporary not of static storage duration is fine during computation of the constant expression. But since the read happens outside of function invocation substitution, the result of function invocation substitution is an lvalue. The respective text of the spec says

A reference constant expression is an lvalue core constant expression that designates an object with static storage duration or a function.

But the reference that max returns yields an lvalue that designates an object of unspecified storage duration. Function invocation substitution is required to yield a constant expression, not merely a core constant expression. So max(sizeof(A), sizeof(B)) is not guaranteed to work.

The following (older) text needs to be read taking the above into account.


I can't see any reason at the moment why you wouldn't want to stick a constexpr there. Anyway, the following code definitely is useful

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

Contrary to what other answers write, I think this is legal. Not all instantiations of max are required to be constexpr functions. The current n3242 says

If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is not a constexpr function or constexpr constructor.

If you call the template, argument deduction will yield a function template specialization. Calling it will trigger function invocation substitution. Consider the following call

int a[max(sizeof(A), sizeof(B))];

It will first do an implicit conversion of the two size_t prvalues to the two reference parameters, binding both references to temporary objects storing their value. The result of this conversion is a glvalue for each case that refers to a temporary object (see 4p3). Now function invocation substitution takes those two glvalues and substitutes all occurences of a and b in the function body by those glvalues

return (<glval.a>) > (<glval.b>) ? (<glval.a>) : (<glval.b>);

The condition will require lvalue to rvalue conversions on these glvalues, which are allowed by 5.19p2

  • a glvalue of literal type that refers to a non-volatile temporary object initialized with a constant expression

The conditional expression will yield a glvalue to either the first or second operand. The unnamed reference max returns will refer to that operand. And the final lvalue to rvalue conversion happening in the array dimension size specification will be valid by the same rule quoted above.


Note that initializer_list currently doesn't have constexpr member functions. This is a known limitation and will be handled post-C++0x, most likely making those members constexpr.

Evidentiary answered 10/4, 2011 at 15:1 Comment(2)
I agree with your reasoning, although I can not quite follow your "rvalue", "glvalue" magic. I saw those definitions in the std, but have to dig in there more deeply, I guess.Duelist
The C++ committee have suggested that it was intended to be possible for function invocation substitution to produce an lvalue referring to a temporary. g++ behaves that way, but clang currently implements the standard as written.Canopus
H
1

The inclusion of constexpr versions of std::min() and std::max() in C++14 demonstrates that there's no fundamental obstacle to making (versions of) these functions constexpr. It seems that this wasn't considered early enough when constexpr was added to C++11.

Obviously, for the versions where a comparison function is provided, that function must itself be constexpr for the template expansion to succeed.

Hak answered 14/7, 2016 at 8:11 Comment(0)
L
-1

min and max are only constant expressions if you call them with constant expressions as arguments. Since they're meant to be much more generally usable than that, you can't make the declaration.

Here's what Wikipedia says about constexpr (emphasis added). I know Wikipedia is not the ultimate reference, but I believe it's correct in this case.

The use of constexpr on a function imposes very strict limitations on what that function can do. First, the function must have a non-void return type. Second, the function contents must be of the form: return expr. Third, expr must be a constant expression, after argument substitution. This constant expression may only call other functions defined as constexpr, or it may use other constant expression data variables.

Landscapist answered 9/4, 2011 at 14:33 Comment(7)
after argument substitutionconstexpr does not disqualify variable arguments.Janice
I sort-of disagree. The very straightforward implementation T max(T a,T b) { return a>b ? a : b; } qualifies to be declared constexpr. This declaration is independent from its use. If I use it with constexpr arguments, I could get a constexpr result.Duelist
Mark's answer is incorrect. At least in C++11, min and max are never constant-expressions, because they are not declared as constexpr in the standard library. But the OP asked: Why aren't they declared constexpr?Sheath
@Quuxplusone, my contention is that if a function is made constexpr then it must always be called with constant expressions as arguments. Do you disagree? Since min and max will almost always be called with non-constant arguments it would be impossible to make them constexpr which is why the standard did not make them so - that was my entire point.Landscapist
@MarkRansom, yes, I disagree. constexpr essentially means "I can be constant", not "I must always be constant". (For the latter case, we already have const variables.) Try this in 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)); }Sheath
@Quuxplusone, you appear to be right, at least for gcc: ideone.com/PmYcA7 So what's the point of constexpr if the compiler is already smart enough to know if the result is constant or not? Is that just a gcc extension?Landscapist
@MarkRansom cprogramming.com/c++11/…Sheath
M
-3

My guess is that, in the general case, operator<(T, T) is not guaranteed to be constexpr either.

Medeiros answered 9/4, 2011 at 13:1 Comment(1)
So, it could be templated, with constexpr applying only to native operand types.Janice

© 2022 - 2024 — McMap. All rights reserved.