Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MSVC and constexpr for function parameter?

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?

like image 584
Antoine Morrier Avatar asked Sep 15 '18 17:09

Antoine Morrier


People also ask

Can a function parameter be constexpr?

We allow annotating a function parameter with constexpr with the same meaning as a variable declaration: must be initialized with a constant expression.

Should I use const or constexpr?

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 .

How do I know if a function is constexpr?

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.

Can a function return constexpr?

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.


2 Answers

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.

like image 45
Shafik Yaghmour Avatar answered Oct 21 '22 05:10

Shafik Yaghmour


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:

  1. v < 5 calls your constexpr operator<
  2. The two parameters are copied (both of them literals and none evaluate to a non-constexpr object)
  3. n2 started its life within the evaluation of v < 5 and is a literal
  4. n is a non-type template parameter, as such usable in a constant expression
  5. Finally, n < 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);
}
like image 115
Rakete1111 Avatar answered Oct 21 '22 05:10

Rakete1111