Is Constexpr a "Hint" (Like Inline) or "A Binding Request" to the Compiler

Is constexpr a hint (like inline) or a binding request to the compiler?

From the C++11 Wiki page:

If a constexpr function or constructor is called with arguments which
aren't constant expressions, the call behaves as if the function were
not constexpr, and the resulting value is not a constant expression.
Likewise, if the expression in the return statement of a constexpr
function does not evaluate to a constant expression for a particular
invocation, the result is not a constant expression.

The constexpr specifier thus expresses the possibility to evaluate something at compile time and is subject to some restrictions when used.


For your particular snippet it seems to me that the C++11 constraint:

exactly one return statement that contains only literal values,
constexpr variables and functions

is not fulfilled, as hash_code is defined to be:

size_t hash_code() const;

In this case the standard draft n3242 says:

For a constexpr function, if no function argument values exist such
that the function invocation substitution would produce a constant
expression (5.19), the program is ill-formed; no diagnostic required.

I believe your example fits here.

When is a constexpr evaluated at compile time?

When a constexpr function is called and the output is assigned to a constexpr variable, it will always be run at compiletime.

Here's a minimal example:

// Compile with -std=c++14 or later
constexpr int fib(int n) {
int f0 = 0;
int f1 = 1;
for(int i = 0; i < n; i++) {
int hold = f0 + f1;
f0 = f1;
f1 = hold;
}
return f0;
}

int main() {
constexpr int blarg = fib(10);
return blarg;
}

When compiled at -O0, gcc outputs the following assembly for main:

main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 55
mov eax, 55
pop rbp
ret

Despite all optimization being turned off, there's never any call to fib in the main function itself.

This applies going all the way back to C++11, however in C++11 the fib function would have to be re-written to use conversion to avoid the use of mutable variables.

Why does the compiler include the assembly for fib in the executable sometimes? A constexpr function can be used at runtime, and when invoked at runtime it will behave like a regular function.

Used properly, constexpr can provide some performance benefits in specific cases, but the push to make everything constexpr is more about writing code that the compiler can check for Undefined Behavior.

What's an example of constexpr providing performance benefits? When implementing a function like std::visit, you need to create a lookup table of function pointers. Creating the lookup table every time std::visit is called would be costly, and assigning the lookup table to a static local variable would still result in measurable overhead because the program has to check if that variable's been initialized every time the function is run.

Thankfully, you can make the lookup table constexpr, and the compiler will actually inline the lookup table into the assembly code for the function so that the contents of the lookup table is significantly more likely to be inside the instruction cache when std::visit is run.

Does C++20 provide any mechanisms for guaranteeing that something runs at compiletime?

If a function is consteval, then the standard specifies that every call to the function must produce a compile-time constant.

This can be trivially used to force the compile-time evaluation of any constexpr function:

template<class T>
consteval T run_at_compiletime(T value) {
return value;
}

Anything given as a parameter to run_at_compiletime must be evaluated at compile-time:

constexpr int fib(int n) {
int f0 = 0;
int f1 = 1;
for(int i = 0; i < n; i++) {
int hold = f0 + f1;
f0 = f1;
f1 = hold;
}
return f0;
}

int main() {
// fib(10) will definitely run at compile time
return run_at_compiletime(fib(10));
}

constexpr function and hardcoded arguments

constexpr int fun(int x, int y) { return x+y; }
fun(5,6) // << constant expression?

tl;dr

5 and 6 are constant expressions. Thus fun(5,6) also is a constant expression and will be evaluated at compile time where this is mandatory (non-type templates for instance).

stuff...
I had a quick look into the standard and I hope I didn't miss any important points.

We already know from @42's answer:

  • According to N4527 int is a valid paramter type for a constexpr function since it is a literal type (since it is a scalar type which is by §3.9/10 of the same document a literal type). Therefore, fun is a valid constexpr function.

  • It provides code that puts fun(5,6) into a context where a constant expression is required and it seems to be accepted by certain compilers.

Now the question is whether this is valid, standard-conformant behaviour.

§5.20 from N4527 says:

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:

  • here comes a large list of things that prevent expressions from being core constant expression

That list does not contain "constexpr function with constant expression arguments" which are therefore core constant expressions (unless they are undefined when used).

Thus, if 5 and 6 are constant expressions, then fun(5,6) is a constant expression if fun is a valid constexpr function and is defined before using it. The given function satisfies the required constraints in §7.1.5/3 and is a valid constexpr function.

Both 5 and 6 are integer literals of type int as per §2.13.2

1) An integer literal is a sequence of digits that has no period or exponent part, with optional separating single quotes that are ignored when determining its value. [...]

2) The type of an integer literal is the first of the corresponding list in Table 5 in which its value can be represented.

Suffix: none, Decimal literal: int, long int, long long int

Now looking at §5.20 again we see: both are constant expressions.

Is a compiler forced to reject invalid constexpr?

By my reading, yes, every compiler must complain about statement (3).

N3242 7.1.5 paragraph 9:

A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. If it initialized by a constructor call, that call shall be a constant expression (5.19). Otherwise, every full-expression that appears in its initializer shall be a constant expression. Each implicit conversion used in converting the initializer expressions and each constructor call used for the initialization shall be one of those allowed in a constant expression (5.19).

I think of a constexpr object as "evaluated at compile time", and a constexpr function or constexpr constructor as "might be evaluated at compile time". A compiler must determine the semantic validity of statements like (3) at compile time. You could argue that the "evaluation" can still be done at run time, but checking for validity does most of that work anyway. Plus, the code could then continue to instantiate a template like Check<y>, which pretty much guarantees the compiler needs to figure out the value of y at compile-time.

This does mean you could write a diabolical program to make the compiler take a really long or infinite time. But I suspect that was already possible with operator-> tricks.

Is it legal to use side-effects in exceptions thrown by constexpr?

It is legal.

For each constexpr function there must be some argument values that result in a constant expression (§7.1.5/5):

For a constexpr function, if no function argument values exist such
that the function invocation substitution would produce a constant
expression (5.19), the program is ill-formed; no diagnostic required.

Note that this does not mean that every possible argument value must result in a constant expression. divide clearly has some argument values that result in a constant expression: divide(1, 1) is a simple example. So, the definition is clearly valid.

But can divide(1, 0) be called? Yes, it can. There's almost no difference between invoking a constexpr function or a "normal" function (§7.1.5/7):

A call to a constexpr function produces the same result as a call to
an equivalent non-constexpr function in all respects except that a
call to a constexpr function can appear in a constant expression.

Note that calls to constexpr functions can appear in constant expressions, but nothing forbids them from not resulting in constant expressions. This is intended so one can call constexpr functions with both compile-time and runtime arguments (otherwise usefulness of constexpr would be severaly limited).

For completeness, let's see what makes a constant expression (§5.19/2):

A conditional-expression is a core constant expression unless it
involves one of the following as a potentially evaluated subexpression
(§3.2), but subexpressions of logical AND (§5.14), logical OR (§5.15),
and conditional (§5.16) operations that are not evaluated are not
considered [...].

So, divide(1, 1) is a constant expression, but divide(1, 0) is not. If you used divide(1, 0) in a template parameter, the program would be ill-formed. But otherwise it's fine.

How is std::mutex's constexpr constructor implemented?

On Windows, it is possible to implement std::mutex as constexpr using SRWLOCK.

Unfortunately full SRWLOCK is available starting in Windows 7. It was introduced in Windows Vista, but without the ability to implement try_lock using it.

Visual Studio 2022 has dropped Windows Vista support, so it could have switched to SRWLOCK, but for ABI compatibility reasons, to be compatible down to VS 2015, it still uses the implementation that allowed runtime selection of sync primitive, to avoid SRWLOCK on pre-Win7.

Technically it is possible to implement std::mutex based on CreateEvent and lazy initialization, in Windows 95 and later, but this implementation would be complex and suboptimal, as it will not use the OS primitives directly and will not allow the OS to be aware of the mutex.


On POSIX, you can use PTHREAD_MUTEX_INITIALIZER unconditionally, even in runtime.

What more does using constexpr gives instead of just static const variables?

You are missing the fact that the use of constexpr in member functions and constructors does not mean that they are always executed in compile-time. They are simply done so when possible.

A few examples for clarity:

  constexpr vec3 myGlobalVec{1,2,3}; // uses vec3(x,y,z) in compile time
constexpr vec3 copyOfGlobalVec = myGlobalVec; // uses copy constructor in compile time

void foo(int n) {
vec3 my(n,n,n); // uses the same constructor, but invoked in runtime
// ...
}

The same applies to the remaining constructors: the key point is that with constexpr-qualified functions and constructors, if the full arguments list contains constant expressions (and if the object at hand is a constant expression), then the result will also be a constant expression.

The advantage of having constexpr getters is that attributes of constant expressions can be obtained for use in cases where only constant expressions are allowed. A float cannot be used as a template parameter, but assuming the vector was made of ints, you could do this:

  SomeClass<myGlobalVec.x()> anObject;

constexpr with operator|=

(Not tested on GCC because I don't have 4.6, but I've verified that the algorithm is correct.)

To use constexpr you must not have assignments. Therefore, you often have to write in functional form with recursion:

#include <cstdint>
#include <climits>

constexpr inline uint64_t highestBit(uint64_t p, int n = 1) {
return n < sizeof(p)*CHAR_BIT ? highestBit(p | p >> n, n * 2) : p - (p >> 1);
}

int main() {
static_assert(highestBit(7) == 4);
static_assert(highestBit(5) == 4);
static_assert(highestBit(0x381283) == 0x200000);
return 0;
}

You can check C++0x §[expr.const]/2 to see what expressions cannot be used in a constexpr function. In particular, the second to last item is "an assignment or a compound assignment".

When should you use constexpr capability in C++11?

Suppose it does something a little more complicated.

constexpr int MeaningOfLife ( int a, int b ) { return a * b; }

const int meaningOfLife = MeaningOfLife( 6, 7 );

Now you have something that can be evaluated down to a constant while maintaining good readability and allowing slightly more complex processing than just setting a constant to a number.

It basically provides a good aid to maintainability as it becomes more obvious what you are doing. Take max( a, b ) for example:

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

Its a pretty simple choice there but it does mean that if you call max with constant values it is explicitly calculated at compile time and not at runtime.

Another good example would be a DegreesToRadians function. Everyone finds degrees easier to read than radians. While you may know that 180 degrees is 3.14159265 (Pi) in radians it is much clearer written as follows:

const float oneeighty = DegreesToRadians( 180.0f );

Lots of good info here:

http://en.cppreference.com/w/cpp/language/constexpr



Related Topics



Leave a reply



Submit