Search

4.13 — Const, constexpr, and symbolic constants

Const variables

So far, all of the variables we’ve seen have been non-constant -- that is, their values can be changed at any time. For example:

However, it’s sometimes useful to define variables with values that can not be changed. For example, consider the gravity of Earth (near the surface): 9.8 meters/second^2. This isn’t likely to change any time soon (and if it does, you’ve likely got bigger problems than learning C++). Defining this value as a constant helps ensure that this value isn’t accidentally changed.

To make a variable constant, simply put the const keyword either before or after the variable type, like so:

Although C++ will accept const either before or after the type, we recommend using const before the type because it better follows standard English language convention where modifiers come before the object being modified (e.g. a “green ball”, not a “ball green”).

Const variables must be initialized when you define them, and then that value can not be changed via assignment.

Declaring a variable as const prevents us from inadvertently changing its value:

Defining a const variable without initializing it will also cause a compile error:

Note that const variables can be initialized from other variables (including non-const ones):

Const is often used with function parameters:

Making a function parameter const does two things. First, it tells the person calling the function that the function will not change the value of myValue. Second, it ensures that the function doesn’t change the value of myValue.

When arguments are passed by value, we generally don’t care if the function changes the value of the parameter (since it’s just a copy that will be destroyed at the end of the function anyway). For this reason, we usually don’t const parameters passed by value. But later on, we’ll talk about other kinds of function parameters (where changing the value of the parameter will change the value of the argument passed in). For these types of parameters, judicious use of const is important.

Runtime vs compile time constants

C++ actually has two different kinds of constants.

Runtime constants are those whose initialization values can only be resolved at runtime (when your program is running). Variables such as usersAge and myValue in the snippets above are runtime constants, because the compiler can’t determine their initial values at compile time. usersAge relies on user input (which can only be given at runtime) and myValue depends on the value passed into the function (which is only known at runtime). However, once initialized, the value of these constants can’t be changed.

Compile-time constants are those whose initialization values can be resolved at compile-time (when your program is compiling). Variable gravity above is an example of a compile-time constant. Compile-time constants enable the compiler to perform optimizations that aren’t available with runtime constants. For example, whenever gravity is used, the compiler can simply substitute the identifier gravity with the literal double 9.8.

When you declare a const variable, the compiler will implicitly keep track of whether it’s a runtime or compile-time constant.

In most cases, this doesn’t matter, but there are a few odd cases where C++ requires a compile-time constant instead of a run-time constant, for example in the instantiation of type -- something we’ll cover later.

constexpr

To help provide more specificity, C++11 introduced the keyword constexpr, which ensures that a constant must be a compile-time constant:

constexpr variables are const. This will get important when we talk about other effects of const in upcoming lessons.

Best practice

Any variable that should not be modifiable after initialization and whose initializer is known at compile-time should be declared as constexpr.
Any variable that should not be modifiable after initialization and whose initializer is not known at compile-time should be declared as const.

Naming your const variables

Some programmers prefer to use all upper-case names for const variables. Others use normal variable names with a ‘k’ prefix. However, we will use normal variable naming conventions, which is more common. Const variables act exactly like normal variables in every case except that they can not be assigned to, so there’s no particular reason they need to be denoted as special.

Symbolic constants

In the previous lesson 4.12 -- Literals, we discussed “magic numbers”, which are literals used in a program to represent a constant value. Since magic numbers are bad, what should you do instead? The answer is: use symbolic constants! A symbolic constant is a name given to a constant literal value. There are two ways to declare symbolic constants in C++. One of them is good, and one of them is not. We’ll show you both.

Bad: Using object-like macros with a substitution parameter as symbolic constants

We’re going to show you the less desirable way to define a symbolic constant first. This method was commonly used in a lot of older code, so you may still see it.

In lesson 2.10 -- Introduction to the preprocessor, you learned that object-like macros have two forms -- one that doesn’t take a substitution parameter (generally used for conditional compilation), and one that does have a substitution parameter. We’ll talk about the case with the substitution parameter here. That takes the form:

#define identifier substitution_text

Whenever the preprocessor encounters this directive, any further occurrence of identifier is replaced by substitution_text. The identifier is traditionally typed in all capital letters, using underscores to represent spaces.

Consider the following snippet:

When you compile your code, the preprocessor replaces all instances of MAX_STUDENTS_PER_CLASS with the literal value 30, which is then compiled into your executable.

You’ll likely agree that this is much more intuitive than using a magic number for a couple of reasons. MAX_STUDENTS_PER_CLASS provides context for what the program is trying to do, even without a comment. Second, if the number of max students per classroom changes, we only need to change the value of MAX_STUDENTS_PER_CLASS in one place, and all instances of MAX_STUDENTS_PER_CLASS will be replaced by the new literal value at the next compilation.

Consider our second example, using #define symbolic constants:

In this case, it’s clear that MAX_STUDENTS_PER_CLASS and MAX_NAME_LENGTH are intended to be independent values, even though they happen to share the same value (30). That way, if we need to update our classroom size, we won’t accidentally change the name length too.

So why not use #define to make symbolic constants? There are (at least) three major problems.

First, because macros are resolved by the preprocessor, which replaces the symbolic name with the defined value, #defined symbolic constants do not show up in the debugger (which shows you your actual code). So although the compiler would compile int max_students { numClassrooms * 30 };, in the debugger you’d see int max_students { numClassrooms * MAX_STUDENTS_PER_CLASS };, and MAX_STUDENTS_PER_CLASS would not be watchable. You’d have to go find the definition of MAX_STUDENTS_PER_CLASS in order to know what the actual value was. This can make your programs harder to debug.

Second, macros can conflict with normal code. For example:

If someheader.h happened to #define a macro named beta, this simple program would break, as the preprocessor would replace the int variable beta’s name with whatever the macro’s value was.

Thirdly, macros don’t follow normal scoping rules, which means in rare cases a macro defined in one part of a program can conflict with code written in another part of the program that it wasn’t supposed to interact with.

Warning

Avoid using #define to create symbolic constants macros.

A better solution: Use constexpr variables

A better way to create symbolic constants is through use of constexpr variables:

Because these are just normal variables, they are watchable in the debugger, have normal scoping, and avoid other weird behaviors.

Best practice

Use constexpr variables to provide a name and context for your magic numbers.

Using symbolic constants throughout a multi-file program

In many applications, a given symbolic constant needs to be used throughout your code (not just in one location). These can include physics or mathematical constants that don’t change (e.g. pi or avogadro’s number), or application-specific “tuning” values (e.g. friction or gravity coefficients). Instead of redefining these every time they are needed, it’s better to declare them once in a central location and use them wherever needed. That way, if you ever need to change them, you only need to change them in one place.

There are multiple ways to facilitate this within C++, but the following is probably easiest:

1) Create a header file to hold these constants
2) Inside this header file, declare a namespace (we’ll talk more about this in lesson 6.2 -- User-defined namespaces)
3) Add all your constants inside the namespace (make sure they’re constexpr in C++11/14, or inline constexpr in C++17 or newer)
4) #include the header file wherever you need it

For example:

constants.h (C++11/14):

In C++17, prefer “inline constexpr” instead:

constants.h (C++17 or newer):

Use the scope resolution operator (::) to access your constants in .cpp files:

main.cpp:

If you have both physics constants and per-application tuning values, you may opt to use two sets of files -- one for the physics values that will never change, and one for your per-program tuning values that are specific to your program. That way you can reuse the physics values in any program.


4.x -- Chapter 4 summary and quiz
Index
4.12 -- Literals

250 comments to 4.13 — Const, constexpr, and symbolic constants

  • Hakan

    Instead of creating variables in namespace with inline & constexpr; using a Helper class/struct to wrap static constexpr variable would be more readable.

  • Thomas

    Hello,
    First of all, thank you for making learncpp.com! It’s a amazing tool for learning C++!

    I’m starting to get quite confused about when it’s ok to use magic numbers it’s not. I’m sorry if my following questions are are badly formulated(english isn’t my first language). Or coming off as ignorant, there is still a lot I don’t know about programming in general and C++ specifically.

    #1 my understanding of what a magic number is:
    I had python at uni but my professor never told us anything about magic numbers, so this sub chapter was the first time I heard about them. As far as I understand is a magic number “A magic number is any number that is not given a name.”  I provide some examples of what would be magic numbers below.

    Based on my understanding would 2, 4, 4, 4, 2 and 0  in Example 1 all be regarded magical numbers. My reasoning for this is that its hard to see witch animal the number relate from the initializations of the array alone.

    In Example 2 are 1 and 100 in line 43 directly used but commented, witch increases the readability of the code but it’s still as far as I understand still magical numbers and should be defined in a variable for increased readability and ease of maintenance.

    Example 1 - From solution of task 2 in the Quiz in chapter 6.2 — Arrays (Part II)

    Example 2 - From  solution to task 2 in 5.x — Chapter 5 comprehensive quiz

    #2 I’m now at 6.3 - Arrays and loops. I’m I right in thinking that the reason that the examples uses so many magical numbers is that the intent of the example is to show how the different concepts works in a as compact form as possible and that’s why you don’t define all the magical numbers?

    #3 Is it any cases where using magical numbers actually is prepared? I see that a lot of people on Quora and in general uses quite a bit of magical numbers. Do you think that this is just for convenience while coding or is it a good reason to do so?

    • nascardriver

      Hello,

      magic numbers are all values that are based on something else in your code or are not obvious from their context. There is no strict definition of magic numbers, so you'll come across different explanations. Comments don't make literals non-magic. Code should be readable without comments.

      Example 1: Not magic. These values are correlated to the animals by index. Although it's not easy to see which animal has which number of legs, the meanings of the numbers is obvious (Number of legs).

      Example 2: Not magic. As in #1, the reader can look up which parameters `std::uniform_int_distribution` takes. If you were to use these values again to print the message in line 50, they'd become magic, because they could change independently but mean the same thing. If you wanted to print those numbers, you should use `die.a()` and `die.b()` or add constants.
      But there's something else in this code. The 'y' in `playAgain` is used more then once, but changing one or the other wouldn't make sense. Clearly, a constant should be used instead. I updated that lesson to use a `while (true)` loop to get rid of the duplicate.

      If a values makes a reader think "Why this value?", it's magic.

      I think it's easiest if you post your quiz solutions in the lessons you're in right now and I'll tell you what's magic and what's not.

  • Keshav

    Missed the #include<iostream> and int main()

  • Keshav

    I am getting an error in this.why?

    the error message is ......

    Error       code:C2678    desription:binary '<<': no operator found which takes a left-hand operand of type 'std::istream' (or there is no acceptable conversion)    project:Testing Subject    file:C:\Users\HP PAVILLION_2\source\repos\Testing Subject\Testing Subject.cpp    line:6    

    here's the code......

    and also I compiled it and works after I close the console and to the error list it still shows the error

  • sami

    "because the compiler can’t determine their initial values at compile time. "

    How come can't the compiler determine those initial values at compile time and STILL won't complain while when we declare an array with size of type a variable "int n {10}; int x[n];" it will complain? Don't compiler have any idea about variables' values during compile time?
    Why can't a compiler know the value of n is 10 "int n{10};" as it is not a user input or a function parameter to be known during run-time ?

    • nascardriver

      Making exceptions is bad. It makes code and compiler implementation a lot more complicated.
      Right now, we say "If a variable is `constexpr`, it's a compile-time constant". Otherwise, it's not. (There is, unfortunately, an exception to this, duh).
      By allowing regular variables to be used in a constant context, it'd have to be specified under which condition it's allowed, so you'd end up with something like "If a variable is `constexpr`, or it is only used in a read-only manner before it is used in a constant context and it is initialized with a value or variable that is usable at compile-time, it's a compile-time constant".
      Needless to say, the first version is a lot simpler. People (and the compiler) don't have to read your code to know if a variable is usable at compile-time. It's only usable at compile-time if you say so (Plus the exception I mentioned before, but won't go into detail).

  • Dean

    I believe the nomenclature of calling a constant a 'constant variable' as used in this lesson is incorrect. A variable is called as such because it can vary. A constant cannot. A constant and a variable are distinctly different and opposite.

  • Jingtong

    For the example of creating a namespace constants, I got an error when I defined a function with the same name constants. So anyone knows about namespace and function name conflict?

  • Alek

    Hello,I've several questions, would be thankful if you could answer these:
    1:in the part you are talking about "making a function parameter const" you say:it tells the person calling the function that the function will not change the value of myValue. Second, it ensures that the function doesn’t change the value of myValue. aren't these two reasoning the same ?

    2:before you talked about constexpr you provided an example which you are using std::bitset in a form.can I ask what it is and what does this piece of code do "std::bitset<numberOfBits> b{};"

    3:can you please provide an example for the third major problem of using object-like macros to define symbolic constants which says: Thirdly, macros don’t follow normal scoping rules, which means in rare cases a macro defined in one part of a program can conflict with code written in another part of the program that it wasn’t supposed to interact with.

    4:what's the difference between inline-constexpr & constexpr.

    thanks & sorry, I'm bombarding you with my questions lately.

    • nascardriver

      1.
      > it tells the person calling the function that the function will not change the value of myValue
      This doesn't make sense yet. When you get to references, you'll understand the difference.

      2.
      Seems like I mixed up some lessons. `std::bitset` is covered later, I removed the example from this lesson.

      3.

      4.
      `inline` variables can have multiple identical definitions, but only 1 variable will actually be created. Without `inline`, you'd create a variable (Which uses up memory) for each definition. For non-const variables, `inline` can also be used to bypass the one-definition-rule.

  • als

    DEFINE EVERYTHING IN A NAMESPACE! (KIDDING, WOULD RESULT IN BREAKING.)

  • Chayim

    For what is it called "symbolic"? It’s a "define" function.

  • Chayim

    Function -numClassrooms- was not declared.

  • Chayim

    How do I post a code snippet on here? I tried to code inside [] but it posts it as text.

  • Bolsonaro

    Can constexpr be declared inside namespaces and forward declared?

    constants.cpp

    constants.h

    main.cpp

    C:\Users\admin\Documents\cbp\test\constants.h|6|error: uninitialized const 'myconstants::myconst' [-fpermissive]|

Leave a Comment

Put all code inside code tags: [code]your code here[/code]