This code compile fine with clang and gcc.
template<size_t n>
struct N {
static constexpr size_t v = n;
};
template<size_t n>
constexpr bool operator<(N<n>, size_t n2) {
return n < n2;
}
template<typename N>
constexpr void foo(N v) {
static_assert(v < 5);
}
int main()
{
foo(N<3>{});
return 0;
}
However, if I use MSVC, I got the error that v < 5
is not a constant expression. I can understand why MSVC thinks that, but I think it is wrong and clang / gcc are right. Is it a bug from MSVC?
We allow annotating a function parameter with constexpr with the same meaning as a variable declaration: must be initialized with a constant expression.
The primary difference between const and constexpr variables is that the initialization of a const variable can be deferred until run time. A constexpr variable must be initialized at compile time. All constexpr variables are const .
The easiest way to check whether a function (e.g., foo ) is constexpr is to assign its return value to a constexpr as below: constexpr auto i = foo(); if the returned value is not constexpr compilation will fail.
A constexpr function is a function that can be invoked within a constant expression. A constexpr function must satisfy the following conditions: It is not virtual. Its return type is a literal type.
MSVC is incorrect here, let's start with a simplified version of the code:
struct N {
static constexpr size_t v = 0;
};
constexpr
bool operator<(N n1, size_t n2) {
return n1.v < n2;
}
void foo(N v) {
static_assert(v < 5, ""); // C++11 does not allow terse form
}
We will look at the static_assert
first, have we violated any rules for constant expressions? If we look at [expr.const]p2.2:
an invocation of a function other than a constexpr constructor for a literal class or a constexpr function [ Note: Overload resolution (13.3) is applied as usual —end note ];
We are good, operator<
is constexpr function and the copy constructor for N
is constexpr constructor for a literal class.
Moving to operator<
and examine the comparison n1.v < n2
and if we look at [expr.const]p2.9:
an lvalue-to-rvalue conversion (4.1) unless it is applied to
- a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
- a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or
- a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression
We are good here as well. In the original example we are referring to a template non-type argument which is usable in a constant expression so the same reasoning applies to that case as well. Both operands of <
are usable constant expressions.
We can also see that MSVC still treats the simplified case as ill-formed even though clang and gcc accept it.
Yes, MSVC is wrong here.
It may seem counter-intuitive that the code is well-formed, because how can v
, which is not a constant expression, possibly be used in a constant expression?
So why is it allowed? Well first, consider that informally, an expression is not a constant expression if it evaluates to a glvalue that is itself not a constant expression or a variable that started its life outside of the the enclosing expression ([expr.const]p2.7).
Second, operator<
is constexpr
.
Now, what happens is that v < 5
is a valid constant expression. To understand that, let's go through the evaluation of the expression.
We have:
v < 5
calls your constexpr
operator<
n2
started its life within the evaluation of v < 5
and is a literaln
is a non-type template parameter, as such usable in a constant expressionn < n2
invokes a builtin operator.All of those do not violate any of the points in [expr.const]p2, so the resulting expression is in fact a constant expression that be used as an argument to static_assert
.
Those types of expressions are known as converted constant expressions.
Here's a simplified example:
struct Foo {
constexpr operator bool() { return true; }
};
int main() {
Foo f;
static_assert(f);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With