Constant Expressions

Packt Publishing
A free video tutorial from Packt Publishing
Tech Knowledge in Motion
3.9 instructor rating • 1420 courses • 349,074 students

Lecture description

Understand what a “constant expression” is and how `constexpr` allows to create functions that can be executed at compile-time

Learn more from the full course

Mastering C++ Standard Library Features

Harness the power of the C++ STL and make full use of its components

06:12:38 of on-demand video • Updated January 2018

  • Analyze and demystify some major new features such as move semantics, variadic templates, and lambdas
  • Learn about new core language features and the problems they were intended to solve
  • Discover new techniques that allow computations to be performed at compile-time
  • Create safer and more convenient interfaces, without any extra hidden cost
  • Improve your code by replacing new/delete with smart pointers
  • Get well versed with the C++ STL and make full use of its components
English [Auto] Programming at compile time in this section we're going to take a look at compile time computations with contacts for manipulation of heredoc template type lists of library features foggers of metaprogramming. Let's begin with the first video Constanta expressions in this video. We're going to take a look at what constant expression means. Dickens sexpert key word in C++ 11 14 and 17 and finally practical use cases of constructs per functions. Let's begin with constant expressions. You might have noticed that values such as non dump the parameters or array sizes do not accept runtime values as an example. If I have a struct called foo here which takes a template parameter and I and I attempt to initialize the struct passing on int i was initialized from a standard input. You will get a compile time error. The error will tell you that I is not a constant expression and can't be used in this context. This makes sense as these values must be known at compile time. The C++ standard refers to expressions whose results can be computed a compile time constant expressions. In this case if I pass 5 which is a numeric literal instead of something that I get the standard input the code will compile as 5 is a constant expression for C++ 11. Only a few language elements were considered valid constant expressions. These include expressions consisting of aeromedical literals and numerators nonvolatile Konst variables or static data members not type template parameters and size of expressions. Since it was mostly Levan and especially in C was post 14. These limitations were greatly relaxed. If you're interested in seeing all the possible language elements that can be considered constant expressions nowadays feel free to take a look at the reference link below. Since suppose post 11 users can not write functions that compute constant expressions. Thanks to the new context per keyword in this code snippet I'm the finding a function called 5. Mark does context error which returns an int but if the function is really simple it's just going to return 5. Thanks with the const expert keyword. I can use 5 in any place where I could. I need a constant expression as an example in this city or re celebration. I can use 5 to set the size of the array before of us plus 11. It was not possible to use a function call as a constant expression usual tricks include using enum or templates that expose the static const member in order to compute a complicated constant expressions in C++ 11 constructs functions were limited to a single return statement. This limitation required developers to either use recursion or a natural code in order to produce useful effects. Here's an example of a context for a function that calculates the Fibonacci number. That way it's implemented is by using a single return statement with a long expression composed of multiple ternary operators. If the past input is zero will return 0 if the input is 1 will return 1. Otherwise we will recurse by something the Bonacci number that was previous to x and x number two times previous to x. Check this out on comparer Explorer. If we check this code out. Uncle polar explorer. You'll see that we are able to use static assert to make sure that our Fibonacci function returns the correct values. This means that invocations of Fibonacci such as this one are constant expressions and can be evaluated and checked the compile time context. Functions can be executed both at runtime and compile time if all the arguments are not compile time. The function might be evaluated at compile time. This is not guaranteed. If any of the arguments is only known at runtime the function cannot be evaluated at compile time and will always require some runtime code to be executed. The function is invoked in a context where a constant expression is required such as the size of an array. Compile time obligation is mandatory if the compiler is not able to evaluate that function at compile time. I never will be produced in that context. It can also be applied to variables. It forces virals to be initialized with a constant expression. In this example If we say const expert int and 0 equals 42 everything is OK because 0 is immediately initialized with a constant expression. Instead we say context for int and 1 without initializing it. We'll get up on time error. Of course those extra variables must be properly initialized. Now imagine that we have a function called read into from standard in and this function simply goes in and returns to integer that the user pass in a terminal. If we attempt to initialize icons exper into the return value of this function we'll get an error as the function is not a constant expression. If we try to mark the function as a constructor we will get another error as a city sin is not usable in sexpert context. Finally the shorter example shows a function called triplicate that given an int will return 3 times that value. This function is marked as const exper if we attempt to initialize int and 3 with a function call to triplicated everything will be fine as the past input argument here is 5 which is a constant expression and triplicated itself is markets constructs for after we do this. We can even use static assert to make sure that and is equal to 15 at compile time. That was an overview of contact's principles plus 11. Let's move on to suppose plus 14. Since suppose 14 consecutive function restriction were greatly relaxed almost any language feature can be used inside the body of a context or function. Except then I make a location runtime based memory accesses and casts such as reinterpret cast expressions that would lead to undefined behavior or function calls to non-const functions. The list continues with more restrictions but the general rule of thumb is if the action you're performing requires some kind of runtime based address or some kind of a location. Denny will not be allowed in a context per function. Otherwise you should be good to go. Here's an example of a feeble implementation tipis suppose 14 that does not use recursion and looks like a normal imperative. Bonacci function as you can see I'm creating local variables A and B to hold the previous two numbers of the Fibonacci calculation. I am then using a regular for loop in order to calculate the Fibonacci sequence in say the for loop mutating local variables and also signing them to a temporary variable. Finally I will return the value of a after the computation. Open opened computer Explorer. You'll see that even though this function looks very different from what you've seen in supposed plus 11 everything can be computed at compile time from the compiler. In fact we can again use static assert in order to verify that everybody implementation is valid during compilation and C++ 17. As you've seen in the previous sections lambdas can finally be context for as well if a lambda returns a constant expression. You will be implicitly const expert. Otherwise the user can place the constructs or keyword between the parameter list and the body of the lambda in order to force it to be constructs. If the user specifies the constructs for keyword and the body of the land is not actually a constant expression then you will get a compile time. In this case using a c b c inside the Lunda is obviously not something that can be done at compile time. So marking it as context error will prevent a program from compiling C++ 17. We also have a very new important feature. Compile time branching. This feature is called contact's read and the syntax looks as follows. Is the keyword if followed by the keyboard constructor and in parenthesis any constant expression which evaluates a boolean. But you can branch on. In this case this function foo is a template that takes an argument x and we will execute this block of code only if t is equal to the string. Otherwise we will execute the order codebook as is same. The utility that's in the type creates header which will return TRUE as a constant expression. If the first type is equal to the second type Let's look more closely at this. If const exper statement Firstly the condition that you provide in the wrong parenthesis must be a constant expression. If the condition is not a constant expression the problem will fail to compile both branches. So the tree branch and the false branch must be parsable. This means that they need to contain vollies C++. In terms of syntax but only to take n branch would be instantiated. This means that in the non-listed to the branch code which is invalid for the current t. In this example is allowed. This will be clear with an example. Imagine it all the implementation of foo that takes a cons the ref and returns an int. Again we'll check if t is equal to a C does drink. Firstly we'll use the regular old if statement in the true branch. Would return X that lets in the else branch will return x. Now if we attempt to invoke foo weirdness the string instance we will first check the condition which is true and we will enter this branch over here and return the end of the string. So far so good but when foo is instance seeded the else statement will be considered as well and it will attempt to return X which is a string which will cause a compile time error because the function foo returns an int. If we try to invoke foo with an int instead what will happen is that this check will return false so we will not enter this branch. We will enter the else branch and return to int which is OK as the return type of the function is int but the entire function will be unseated including this extra blank access which is obviously not valid for the type int. The problem here is that even Dodi's Asadi seem. The check is a constant expression that if statement doesn't support arbitrarily discarding a branch depending on the result of the check. Both branches will be unconditional to instantiate it and need to be valid for the type T. Let's not take a look at the same function but with an if constructs instead of a regular F.. In this case if we invoke foo witnessed the string instance this check will return true. So we will only instantiate the true branch which attempts to return X that lent. This is valid for this drink. Note that the else branch was not being considered allowing the function to properly compile. On the other hand if we called foo an INT What will happen is that this check will return false so we will only instance see the else branch which return x. That is a valid Ent. match in the signature of the function. Again the true branch in this case will not be instance he added. So X that linked doesn't need to be valid for int. That's it for Suppose 17. But what does the future hold for constant expressions. There are proposals being discussed in the superstars that are committee that would allow dynamic memory allocation and constructs functions something like a constructs for allocate or was proposed to allow usage of the sum of library containers in concepts or functions the idea is that constructs or functions might become as powerful as any other function sometime in the future. This would allow you to use data structures much of the city map as it is string or vector as part of the body of the constructs per function within an overview of what can sexpert can do in 11 14 and 17. But why would you want to use const expert Gideons that context functions allow us to produce constant expressions remember that if they are invoked we're on to arguments they will not produce constant expressions. Constant expressions can be used for compile time data structures and algorithms performing computations compile time is faster and safer than doing them at runtime. There are some reasons for that but the main ones are compile time errors will be used instead of runtime errors. This allows you to catch behavior. That's incorrect during the compilation progress and not when your program is deployed and code will not be generated or executed at runtime. The compiler will have a chance to call pute the results of your functions compile time and possibly aggressively in line those results in order to produce a faster and smaller binary. Here's an example of a useful function I suppose plus 14 factorial in this case I'm using a result local variable here that starts from 1 and then I'm going to loop from one to the provided in the integer n and for each iteration of the loop I will multiply the result variable. By I. And finally I return the value again. Looking at computer explorer would prove that this function is correct and executed at compile time using factorial to initialize a const expert in fireable will force compile time evaluation of the function. We don't use static assert in order to verify the results. If we didn't choose context for when initializing a viable from factorial then we have no guarantee that factorial 4 will be evaluated at compile time. It might but it might also be able to get at runtime even though the arguments are old cons that expressions. In fact using static assert here would result in a compile time error. We need to use the old regular runtime assertion. Finally if we use something like the DC in we are forcing factorial to be evaluated at runtime because we have no way of knowing the value of input before we execute the program. Computing functions like factorial a compulsion could be really useful when you're dealing with data structures that depend on those results. Imagine that you want to store a set of every possible permutation of some widget class and turn on an array. You can compute the amount of mutations of the widgets. If you know the counter compile time by simply invoking factorial in order to initialize the array size since factorise cons exper we can use it instead of hard coding the result or having to switch array for a runtime of data structure. Additionally the factorial function either can and should be unit tested especially by using static assertions if you compare this to having to recalculate and hard code a value of the the factory of the widget count every time the count changes then you will see that this is much more safer and maintainable. Let's not take a look at a completely different example. Validating strings at compile time. Imagine that we have some piece of software where user IDs are strings that begin with a capital you and I dot and we want to verify whether a string is a valid user ID at compile time what we could do is create a context for bool function call is valid user ID that takes any ID of type T and then returns true if the size of ID is greater increment free. And if the first character is equal to uppercase you and the second character is equal to a dot. So this is mackerels constructs. And since this is a template it will be valid for any type that behaves like a string both a runtime and compile time. You will notice that we're using a sightseer. This is a new utility introduced in both 17 that homogeneously allows you to retrieve an array or a container size. This will work with stuff like vector the string but also societ race. Also as mentioned this is a template. This means that it will both work with the string and constructors are allowing you to use it both at runtime and compile time. Again we can see that using static searches are allowed here and we can verify that a string beginning with an adult is indeed a valid user ID and a string that doesn't begin with a prefix is not a user id the exact same function will also work with values that are only known at runtime. If we use the CDC and to read a string from the terminal then we will be able to use assert and call is valid user ID. This will allow you to have a single implementation that works with any type that behaves like a string. A runtime and compile time. The tunnel is really deep. You can do a lot of crazy stuff with const exper. There are libraries such as spruit or Yuuki which pushed the limits of context power by providing complicated algorithms and data structures that can be modified and read at compile time. There is also very interesting talk and entertain talk by the dean and Jason Turner called constat for older things which shows how to implement a compile time JS and parser as you can see from this screenshot. By combining functional programming utilities and new library features such as a city street view it is possible to create a context for a function that parses part of adjacent object later in the talk. They demonstrate how it's possible to use contex power in order to completely parse a complete object to parse a complicated J-Zone object. Here's a recap for this video. That sort of functions can evaluate to constant expressions. They might be able to get it to compile time if all the provided arguments are considered expressions or they might be evaluated runtime waterwise context variables must be initialized with a constant expression. If you invoke a constructor function to initialize the context variable it will be guaranteed to be evaluated at compile time in C++ 14 consecutive functions do not have many limitations as an 11 11. They are limited to a single return statement but M-14 you can use many language features in 17. We have expert Lumb does and compile time brunching with exper. Here are some guidelines that you need to remember. Always define your compile time constant as context. Even if they are simple. This will allow you to use a name for otherwise magic numbers and also explicitly specify that your intent is not to mutate this constant and that should be known at compile time. You can use this for numbers such as this frame rate here for strings such as this debate category for some hypothetical LUGG and even for floats and initialized that with a constant expression. Even in a local context context two variables can increase readability. Imagine that you have this read file function which takes a path and then returns a file and you want to specify the size of a chunk in order to read the file in a buffered manner. If you know your chunk size is compile time and if you know that it's not going to change it is very beneficial to market as contacts. Even though it's a local viable This will allow people reading the code to know what it is supposed to be an immutable compile time constant. You should try to mark your functions with context for whatever possible. And also consider using IF construct sper. If you have access to supposable 17 and are implementing some sort of static dispatch look for opportunities to write and test constructs or functions instead of hard coding pre-calculated computations. This includes constants resizes loop conditions and validation routines. That's it for this video in the next video we're going to take a look at context in the standard library.