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 (such as when defining the length of a fixed-size array -- we’ll cover this later), or to declare a std::bitset.

otherNumberOfBits is a run-time constant, because getNumberOfBits might do something that requires the program to be running, eg. using std::cin.

constexpr

To help provide more specificity, C++11 introduced new 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

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

  • Yolo

    The last example mentioned creates a warning error(if warning level 4 and treat warnings as errors is selected).

    It says the following:" Arithmetic Overflow: Using the operator '*' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling '*' to avoid overflow."

    Gets corrected if u replace int with double. Yet i don't understand how it results to an overflow, when 8 byte values are supposed to have no problems storing or processing smaller,in capacity, values.

    If can someone explain,I would be glad.

    • nascardriver

      could overflow (If radius > INT_MAX/2) when we could just be using

      I updated the lesson to use 2.0 instead. Double literals should be used for doubles.

  • Jason

    You say: "In C++17, prefer “inline constexpr” instead", however specification says that constexpr is implicitly inline in C++17, which means you don't need to include the identifier.

  • matanya679

    hello guys,first of all i just want to say those tuturials are great and im learning a lot from them.
    im finding it a bit confusing that its recommended to create those const variables inside a header file because even though its under header guard it may be added to multiple different cpp files and after compilation the linker will add all of them altogether as a .o files and then we will have more than one definition of those variables and hence will have duplicated definition.

    • nascardriver

      Global `const` variables have internal linkage by default, which means that definitions in different source files don't collide with each other. We talk more about this in chapter 6.

  • Zeni

    At one place you mention "Const variables must be initialized when you define them" , but isn't the run time constant CAN be defined without initialization (and later be assigned a value)?

  • Matthew

    I am getting an error when I use 'inline'..

    inline specifier allowed on function declarations only.

    I used the last example.

  • Wambui

    The last main.cpp needs '() ' in the int main.
    Keep up the good work!

  • salah

    in the previous lessons we were taught that every time we include a header file, we are actually make a copy of the content into the .cpp files, so how using "inline" would prevent wasting memory :  the copy of the constexpr variables ??

    • nascardriver

      (Non-static) `inline` variables are created only once, no matter how often they are defined. Even if an `inline` variable's definition is copied into multiple source files, it's only created once (It's the same variable in every file).

  • HZ

    A hopefully useful detail: review "For example, whenever gravity is used, the compiler can simply substitute the identifier gravity for the literal double 9.8.", where I suppose you mean "substitute with". It appears to me as if it now reads exactly the reverse of what you intend to explain.
    Cheers!

    • nascardriver

      Yep,
      substitute old with new
      substitute new for old
      Lesson amended, thanks!

      • HZ

        :-) I am quite fond of English, however, it isn't my native language, so I had to verify my hunch before writing the comment. Your old/new new/old "template" (to stay in tune with our subject matter here) is quite a nice one to check which way around gives the correct sentence!
        HZ

  • HolzstockG

    Why do we put constants inside of header file and not in source code file which we could add? Is there even a possiblity to create forward declaration for variables?

    • Raton

      I think it's recommended not to #include .cpp files inside .cpp files. A .cpp file is supposed to contain normal statements (not just declarations), which you probably don't want to #include in other .cpp files. And a header file is supposed, I think, to contain declarations, which is exactly what is needed here.

      And I think you can forward declare variables using the extern keyword but I'm not sure.

  • HolzstockG

    You can add here as an reminder from previous lesson that one of the cases of having to use constant during compile-time is defining std::bitset<> next to the arrays.

    • nascardriver

      I added an example with `std::bitset` in the run-time/compile-time section. Thank you for the suggestion!

      `constexpr` variables cannot be forward declared, they have to be initialized at their declaration. The `inline` prevents the variables from being defined multiple times.
      Other variables can be forward declared, this is covered later.

  • Eshita Rastogi

    Hello
    while compiling main.cpp and constants.h, I am getting an error in the constants in the following line

    double circumference { 2 * radius * constants::pi };

    The error is as follows
    name followed by '::' must be a class or namespace name

    Can anyone explain what is the meaning of this error?

    • Alex

      It's not recognizing "constants" as a namespace. That either means you have a typo somewhere (e.g. in constants.h you named your namespace "constant" or "costnants" or something, or you forgot to #include "constants.h" from main.cpp.

  • BooGDaaN

    At the end of this setion, you need to include also:

    Great practice and very useful!

  • kiwi

    Under the "Runtime vs compile time constants" header, the third sentence states: "Variables such as usersAge and myValue in the snippets above above are runtime constants..."

    I think you meant to type shown above instead of above above. Just wanted to let you know so someone else doesn't get confused.

    Thanks for the comprehensive tutorials!

  • Mike

    From Ch 4.13
    "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..."

    When I tested this using a run-time constant in Visual Studio, I could never get it to compile. The operator>> before the variable name would always show a red squiggly line. The compiler would return many errors starting with error C2593:  'operator >>' is ambiguous

    Now I understand, const means it can't be changed, but why doesn't the compiler just state that to the user. Instead, it's making the previous operator/command in the statement look like it's the culprit, thus resulting in like 10 or 15 errors.

    The lesson touched on run-time constants, but I don't believe it elaborated on how to actually do/code it to work.

    I know the code below is not recommended, but shouldn't it still work, as Alex stated above regarding run-time constants: "In most cases, this doesn’t matter"?

    • > I know the code below is not recommended, but shouldn't it still work
      No, it shouldn't work. It doesn't matter is `range` is a compile-time constant. It's `const` and can't be changed, so `std::cin >>` can't assign to it.

      The error is shown on `>>` because `>>` is a function (You'll learn that later). There are multiple versions of this function (You'll learn that later), but none of them takes a `const int` as an argument. So you're trying to use a function that doesn't exist, hence the error on the `>>`.

  • Jon

    That last main.cpp could use

  • Marie Bethell

    Hi!
    What does inline do?
    Thanks.

  • Georgii

    Warning. This comment has nothing to do with programming.
    In the second paragraph you say: "For example, consider the value of gravity on Earth: 9.8 meters/second^2."
    Typical programmer language :p   )))
    The value of gravity... Well I'd say it's pretty darn precious, because I really like being able to lie down and relax )
    A better option would have been "Gravitational acceleration on Earth" or "Freefall acceleration on Earth".
    Awesome tutorials btw.
    Thanks

    • Alex

      Heh. I changed it to "Gravity of Earth" rather than "Gravity on Earth", which Wikipedia defines as, "the net acceleration that is imparted to objects due to the combined effect of gravitation (from distribution of mass within Earth) and the centrifugal force (from the Earth's rotation)".

  • Nguyen

    Hi,

    Sometime I see const placed before the function's return type and/or parameter's type as shown below:

    Example 1

    Example 2

    Could you please explain const in these examples?

    Thanks

  • I believe when we use const with pointers then the meaning of using them before or after the declaration type does change. May be you can explain that as well.

  • BP

    Hey!
    A quick question, is there a good reason for:

    This is done in the last example and I don't really know why you wouldn't use uniform initialization.
    Is there a reason?
    Thanks!

  • Anders

    Could you write something about the use of

    together.
    As a reader you almost get the idea that you would use either constexpr OR const.

    • Alex

      For objects, "constexpr const" is redundant when both keywords refer to the same object. In such a case, you can use constexpr.

  • Sammy

    I did some research and found out:
    const can use on both runtime and compile-time, while
    constexpr can use on compile-time only.

    Why don't we just use const, so that we don't have to think which time are we on?
    Could you explain more why constexpr is the best practice?
    The above lesson is not clear enough for me. Thanks.

    • If you can do computations at compile-time, you don't have to do them every time the program runs. const will calculate on every run, constexpr will calculate once during compilation.
      There are also constexpr and consteval functions, which can do even more computations at compile-time and they won't work with const.

      • Ganesh Koli

        Hello nascardriver,
        Before constexpr as a part of C++11, Do compiler optimize the code to do computation at compile time , which later added in C++ standard.

        I believe most of the compiler do optimization with respecting the C++ standard(i mean without violating C++ standard) like copy ellision, run time optimization.

        Thanks to clear my view. and provide more details.

        • Compilers did and still do computations at compile time if they think it improves the program. `constexpr` is only a hint for compilers so it's easier for them to find functions which can be optimized. They don't have to run `constexpr` functions at compile-time.

      • salah

        Hi nascardriver,
        Do you mean that the const variables will be calculated (again) by the compiler every time they used .Rather, constexpr will be calculated once when the are declared which ,in turn, make the program faster ??

        • nascardriver

          Both `constexpr` and `const` might get calculated multiple times. The difference is that `constexpr` variables can be used at compile-time, whereas regular `const` might require the program to run.
          I can't provide a full example this early in the lessons, so here's one without code:
          Say you have a word that's known at compile-time, "salah", and you want to count how many 'a's are in that word. The answer is 2, and the answer will always be 2. With `constexpr`, the letters will be counted when you compile the program, and just the answer is stored in the binary. When you run the program, it knows there are 2 'a's in "salah" without counting the letters, because that already happened during compilation.
          With regular `const` or without `const`, the program counts the letters every single time your run the program. That's wasteful, because the answer is always the same.

          (The compiler may or may not follow the suggestion of calculating something at compile-time or not. Most compilers do as much as possible at compile-time. Using `constexpr` helps the compiler decide what can be done at compile-time).

  • Conor

    Can you use constexpr in C?

  • Gejsi

    Can I name the namespace header file the exact same word as the namespace identifier, or is it a bad practice and gives a compile error?
    IE:

    • * Line 6, 7, 8: Initialize your variables with brace initializers. You used direct initialization.

      If the name that describes the contents of the file and the one that describes the contents of the namespace are the same, then you should name the file and namespace the same or similar. There won't be any errors.

  • In the "#define myconstant 2" vs "const int myconstatant = 2" discussion, I do wonder about one thing. I don't expect to often have to deal with it, but I recently wrote a small tool in C and I had to change my entire code when I wanted to make it MS-DOS compatible due to things not being fully supported. If I would ever code a tool which I intend to make runnable in DOS (either on real MS-DOS or DOSBox), should that 'force' me to use the "#define" method or is const also present in C++ compilers that age?

    • MS-DOS is an OS, C++ is a language, they're unrelated to each other.
      The closest guess I have is that you're running a compiler in MS-DOS that doesn't know about most of C++'s revisions (It probably only knows C++98). If that's the case, you can use the clang++ status site the see the changes in each version of C++ ( https://clang.llvm.org/cxx_status.html ).
      const has always been there.
      If you're talking about something else, please let me know.

  • Okorie Emmanuella

    Hello Nascardriver!

    is 2 not a magic number in the above snippet?

    • Hi!

      No. The formula for the circumference is well known (At least in combination with the variable name "circumference"). Also, there's no good name you could give to 2 in this case.
      Magic numbers occur when you don't name numbers that you made up yourself, like the height of a tower, a maximum score, etc..

  • Louis Cloete

    @Alex, if I find that I repeat a block of code similar to this:

    Would it make sense to define it as an object-like macro, then #undef'ing the macro after the fourth time? Just to be clear, it can not be inside a loop, because I change variables x and y between each time this sequence is executed.

    • Don't use macros unless you have to.
      You give little context about your code, here's my suggestion.

      • Louis Cloete

        The context is that I am writing a PvP chess game. The function takes a board coordinate as input and figures out if a Knight threatens a given square. Here it is:

        It is a bit ugly. It also has a lot of unsigned ints, because of std::array's sizeType. The gist is that if the change in one direction is +/- 2, the change in the other direction must be +/- 1, and vice versa. You gave me an idea, anyway. I can post when I implemented it if you want to.

        • The size type of @std::array doesn't have to be an unsigned int. Use boardArray_t::size_type instead.
          @searchPos in @findPiece never changes. There was also a spelling error in a variable name that prevented compilation. Make sure to test your program often during development to prevent getting overrun by problems in the end.
          My untested solution (Write a solution yourself first, post that)

          SPOILER SPOILER SPOILER

          • Louis Cloete

            I decided against using boardArray_t::sizeType, because I have an array of Pieces too, which is technically another type, and I want to use the same loop counter to access both sometimes. std::array::sizeType is an alias for unsigned int, so I use it. I would prefer to use ints, but the compiler doesn't like it. Maybe I should just remove the -Werror flag and use ints.

            Thanks for pointing out that searchPos never changes. I missed that.

            The spelling error was because I wrote the code with Afrikaans variable names. I copied it into the comment editing box and did a manual search and replace with English names. @board was originally @bord. I did compile just prior to asking my original question and didn't change anything since then until I copied the code into the comment editing box.

            My code:
            Note: I didn't use classes, since I started this project before I knew how to use them. I decided to finish it without using my own classes, then refactor it into classes as applicable. I also didn't translate again. You should be able to follow the gist of the code after the previous translated version.

            • * Line 5, 35, 36: Initialize your variables with uniform initialization.
              * Line 33, 49: Limit your lines to 80 characters in length for better readability on small displays.

              > Maybe I should just remove the -Werror flag and use ints.
              No. Error and warnings mean that you're doing something wrong. Disabling them will lead to trouble sooner or later.

              > I wrote the code with Afrikaans
              Program in Dutch and dutch people will help you. Program in English and the world will help you.

              * @soekStuk would be more efficient to use if it returned the Square it found, as any caller will most likely want to access the piece.
              * @pos should be declared in line 49
              * @stuk should be declared in line 53
              * @soekStuk can return by value. This will make your code easier and less prone to memory leaks. This won't slow down your code. Compilers have to elide the copy.

              • Louis Cloete

                Hi, @nascardriver!

                I have been afc for a while. I think you misunderstood my intention with the -Werror flag. Of course you shouldn't disable warnings. It is just that the choice to use unsigned ints for std::array's size type generates a lot of sign conversion warnings. Some of them are legit, and should be dealt with, while some of them are no problem. I can see that my int loop counter will never go negative. I am just counting from 0 up to 7 in a lot of cases. To fix that, I have to either static_cast to unsigned int (ugly) or use unsigned ints for loop counters (not always possible in my program for other reasons). Disabling -Werror will still give the warnings, just not stop compilation on them. This will allow me to "fix" the legit ones without bloating my code fixing bogus errors.

                About coding in English, I didn't think I would share this code with anyone except for personal programmer friends speaking Afrikaans. I always code in English if I intend to share / ask a wider audience.

                About the last asterisks: I return a pointer because you can do this with a pointer:

                It is also a very handy way to encode the information if an actual piece was found in the search to return nullptr for a blank search. What should I do to make it clear to the caller that no legal Piece was found in the current search if I return by value?

                I agree that it is better to return the Square, so I made the @soekStuk return a Square*

                • > Werror
                  If you remove -Werror and ignore the warnings about signed/unsigned mismatches you'll miss other warnings that might not be negligible, because you get used to seeing warnings. You might not like @static_cast now, but after using it for a while you'll no longer care about it.

                  > @soekStuk
                  3 options I can think of right now:

  • Jules

    #include "constants.h"
    double circumference = 2 * radius * constants::pi;

    Instead can I use :
    double circumference = 2 * radius * constants.pi;

    what is the use of the "." operator? is it discussed later?

    And whenever "std::cout" or "std::cin" is used, are cin and cout functions being accessed from the standard library using the scope resolution operator? I am confused because cin and cout dont seem to be constants

  • HUE Saki

    PLEASE HELP ME!

    error: invalid operands of types 'int' and 'double()' to binary 'operator*'

    why above error occur when I try to compile below program in my CODE::BLOCK

    CONSTANT.H

    ifndef CONSTANTS_H
    #define CONSTANTS_H

    // define your own namespace to hold constants
    namespace constants
    {
        constexpr double pi(3.14159);
        constexpr double avogadro(6.0221413e23);
        constexpr double my_gravity(9.2); // m/s^2 -- gravity is light on this planet
        // ... other related constants
    }
    #endif

    MAIN.cpp

    #include <iostream>
    #include "CONSTANT.H"

    int main()
    {

    double circumference = 2.0 * 7.0 * constants::pi;
    return 0;}

    • hailinh0708

      1. You forget the "#" sign before ifndef.
      Aside from that, it compiles fine for me.
      Note: The compiler error(s) that appears do not relate to the error you encountered.

Leave a Comment

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