When Should You Use Constexpr Capability in C++11

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

Should we use constexpr everywhere we can?

It won't bother the compiler. The compiler will (or should anyway) give you a diagnostic when/if you use it on code that doesn't fit the requirements of a constexpr.

At the same time, I'd be a bit hesitant to just slap it on there because you could. Even though it doesn't/won't bother the compiler, your primary audience is other people reading the code. At least IMO, you should use constexpr to convey a fairly specific meaning to them, and just slapping it on other expressions because you can will be misleading. I think it would be fair for a reader to wonder what was going on with a function that's marked as a constexpr, but only used as a normal run-time function.

At the same time, if you have a function that you honestly expect to use at compile time, and you just haven't used it that way yet, marking it as constexpr might make considerably more sense.

Why is C++11 constexpr so restrictive?

The reason you'd need to write statements instead of expressions is that you want to take advantage of the additional capabilities of statements, particularly the ability to loop. But to be useful, that would require the ability to declare variables (also banned).

If you combine a facility for looping, with mutable variables, with logical branching (as in if statements) then you have the ability to create infinite loops. It is not possible to determine if such a loop will ever terminate (the halting problem). Thus some sources would cause the compiler to hang.

By using recursive pure functions it is possible to cause infinite recursion, which can be shown to be equivalently powerful to the looping capabilities described above. However, C++ already has that problem at compile time - it occurs with template expansion - and so compilers already have to have a switch for "template stack depth" so they know when to give up.

So the restrictions seem designed to ensure that this problem (of determining if a C++ compilation will ever finish) doesn't get any thornier than it already is.

What's the difference between constexpr and const?

Basic meaning and syntax

Both keywords can be used in the declaration of objects as well as functions. The basic difference when applied to objects is this:

  • const declares an object as constant. This implies a guarantee that once initialized, the value of that object won't change, and the compiler can make use of this fact for optimizations. It also helps prevent the programmer from writing code that modifies objects that were not meant to be modified after initialization.

  • constexpr declares an object as fit for use in what the Standard calls constant expressions. But note that constexpr is not the only way to do this.

When applied to functions the basic difference is this:

  • const can only be used for non-static member functions, not functions in general. It gives a guarantee that the member function does not modify any of the non-static data members (except for mutable data members, which can be modified anyway).

  • constexpr can be used with both member and non-member functions, as well as constructors. It declares the function fit for use in constant expressions. The compiler will only accept it if the function meets certain criteria (7.1.5/3,4), most importantly (†):

    • The function body must be non-virtual and extremely simple: Apart from typedefs and static asserts, only a single return statement is allowed. In the case of a constructor, only an initialization list, typedefs, and static assert are allowed. (= default and = delete are allowed, too, though.)
    • As of C++14, the rules are more relaxed, what is allowed since then inside a constexpr function: asm declaration, a goto statement, a statement with a label other than case and default, try-block, the definition of a variable of non-literal type, definition of a variable of static or thread storage duration, the definition of a variable for which no initialization is performed.
    • The arguments and the return type must be literal types (i.e., generally speaking, very simple types, typically scalars or aggregates)

Constant expressions

As said above, constexpr declares both objects as well as functions as fit for use in constant expressions. A constant expression is more than merely constant:

  • It can be used in places that require compile-time evaluation, for example, template parameters and array-size specifiers:

      template<int N>
    class fixed_size_list
    { /*...*/ };

    fixed_size_list<X> mylist; // X must be an integer constant expression

    int numbers[X]; // X must be an integer constant expression
  • But note:

  • Declaring something as constexpr does not necessarily guarantee that it will be evaluated at compile time. It can be used for such, but it can be used in other places that are evaluated at run-time, as well.

  • An object may be fit for use in constant expressions without being declared constexpr. Example:

         int main()
    {
    const int N = 3;
    int numbers[N] = {1, 2, 3}; // N is constant expression
    }

    This is possible because N, being constant and initialized at declaration time with a literal, satisfies the criteria for a constant expression, even if it isn't declared constexpr.

So when do I actually have to use constexpr?

  • An object like N above can be used as constant expression without being declared constexpr. This is true for all objects that are:
  • const
  • of integral or enumeration type and
  • initialized at declaration time with an expression that is itself a constant expression


[This is due to §5.19/2: A constant expression must not include a subexpression that involves "an lvalue-to-rvalue modification unless […] a glvalue of integral or enumeration type […]" Thanks to Richard Smith for correcting my earlier claim that this was true for all literal types.]

  • For a function to be fit for use in constant expressions, it must be explicitly declared constexpr; it is not sufficient for it merely to satisfy the criteria for constant-expression functions. Example:

     template<int N>
    class list
    { };

    constexpr int sqr1(int arg)
    { return arg * arg; }

    int sqr2(int arg)
    { return arg * arg; }

    int main()
    {
    const int X = 2;
    list<sqr1(X)> mylist1; // OK: sqr1 is constexpr
    list<sqr2(X)> mylist2; // wrong: sqr2 is not constexpr
    }

When can I / should I use both, const and constexpr together?

A. In object declarations. This is never necessary when both keywords refer to the same object to be declared. constexpr implies const.

constexpr const int N = 5;

is the same as

constexpr int N = 5;

However, note that there may be situations when the keywords each refer to different parts of the declaration:

static constexpr int N = 3;

int main()
{
constexpr const int *NP = &N;
}

Here, NP is declared as an address constant-expression, i.e. a pointer that is itself a constant expression. (This is possible when the address is generated by applying the address operator to a static/global constant expression.) Here, both constexpr and const are required: constexpr always refers to the expression being declared (here NP), while const refers to int (it declares a pointer-to-const). Removing the const would render the expression illegal (because (a) a pointer to a non-const object cannot be a constant expression, and (b) &N is in-fact a pointer-to-constant).

B. In member function declarations. In C++11, constexpr implies const, while in C++14 and C++17 that is not the case. A member function declared under C++11 as

constexpr void f();

needs to be declared as

constexpr void f() const;

under C++14 in order to still be usable as a const function.

Why use constexpr

One of the key things that constexpr gives you that was not really possible to achieve before is the ability to call a function in a context that requires a compile time constant. The simplest example of this is an array size:

#include <iostream>

using namespace std;

constexpr auto square(int x) { return x * x; }

int main()
{
int arr[square(2)] = { 0, 1, 2, 3 };
cout << arr[square(1)] << endl;
}

Without the constexpr on function square() it could not be called in the definition of arr since an array size is required to be a compile time constant.

While you could write a template that would give you a compile time square prior to constexpr, you could not use that template with a non compile time constant argument so you would end up with code duplication for compile time and non compile time versions. The syntax for a template is more complicated and less familiar to most programmers than a simple function definition as well.

In fact constexpr makes very few guarantees about when a compiler will actually choose to evaluate something at compile time. In contexts where a value is required to be a compile time constant (such as an array size) it will be of course. In other contexts it's largely up to the compiler - in the call to square() when accessing arr[square(1)] for example the compiler is free to evaluate at runtime, although in practice I would expect most compilers to evaluate this at compile time, at least in an optimized build.

Calling processing constexpr at runtime. C++

I know that constexprt functions are processed at compile time.

Inaccurate. A constexpr function may be used where a constant expression is required, if the stars align. That means it must satisfy certain requirements, but it's still a function. And you can still use it as one.

In your case the function is compiled and called at run-time.

If you were to use it where a constant expression is required, and there the branch with the throw was used, then you'd be seeing a stream of problem coming your way.

When should one set the constructor as constexpr?

By making the constructor constexpr, you allow users to create constexpr objects, and use them in their own constant expressions. This makes the class friendlier in some circumstances; for example, when programming for an embedded system where you want to put data in read-only memory if possible.

So, from the point of view of making the class as flexible and generally useful as possible, you should do so if you can.

constexpr concept in c++11

constexpr auto e2 = &z[20] - &z[3]; 

just calculates the offset between the third and the 20th element. So there's no need to know the adresses.

On the other hand the following example does not work, because the addresses of z[20] and t are evaluated at runtime.

int z[30];
int t;
constexpr auto e2 = &z[20] - &t;

As pointed out by Passer By this is undefined behaviour according to the standard (7.6.6 Additive operators, last sentence):

Unless both pointers point to elements of the same array object, or
one past the last element of the array object, the behavior is undefined.

Purpose of constexpr

Before constexpr the compilers could sometimes figure out a compile time constant and use it. However, the programmer could never know when this would happen.

Afterwards, the programmer is immediately informed if an expression is not a compile time constant and he or she realizes the need to fix it.



Related Topics



Leave a reply



Submit