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 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 for 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).

constexpr

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

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 S.4.3b -- Namespaces)
3) Add all your constants inside the namespace (make sure they’re constexpr)
4) #include the header file wherever you need it

For example:

constants.h:

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

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

  • 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.

  • 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]