Search

6.11a — References and const

Reference to const value

Just like it’s possible to declare a pointer to a const value, it’s also possible to declare a reference to a const value. This is done by declaring a reference using the const keyword.

A reference to a const value is often called a const reference for short, though this does make for some inconsistent nomenclature with pointers.

Initializing references to const values

Unlike references to non-const values, which can only be initialized with non-const l-values, references to const values can be initialized with non-const l-value, const l-values, and r-values.

Much like a pointer to a const value, a reference to a const value can reference a non-const variable. When accessed through a reference to a const value, the value is considered const even if the original variable is not:

References to r-values extend the lifetime of the referenced value

Normally r-values have expression scope, meaning the values are destroyed at the end of the expression in which they are created.

However, when a reference to a const value is initialized with an r-value, the lifetime of the r-value is extended to match the lifetime of the reference.

Const references as function parameters

References used as function parameters can also be const. This allows us to access the argument without making a copy of it, while guaranteeing that the function will not change the value being referenced.

References to const values are particularly useful as function parameters because of their versatility. A const reference parameter allows you to pass in a non-const l-value argument, a const l-value argument, a literal, or the result of an expression:

The above prints

1234

To avoid making unnecessary, potentially expensive copies, variables that are not pointers or fundamental data types (int, double, etc…) should be generally passed by (const) reference. Fundamental data types should be passed by value, unless the function needs to change them. There are a few exceptions to this rule, namely types that are so small that it’s faster for the CPU to copy them than having to perform an extra indirection for a reference.

A reminder

References act like pointers. The compiler adds the indirection, which we’d do manually on a pointer using an asterisk, for us.

One of those fast types is std::string_view. You’ll learn about more exceptions later. If you’re uncertain if a non-fundamental type is fast to pass by value, pass it by const reference.

Rule

Pass non-pointer, non-fundamental data type variables (such as structs) by (const) reference, unless you know that passing it by value is faster.

Quiz time


Question #1


Which of the following types should be passed by value, which by const reference? You can assume that the function which takes these types as parameters doesn’t modify them.

a) char
b) std::string
c) unsigned long
d) bool
e) An enumerator
f)

g)

h) double
i)

For reference, this is how we’d go about using ArrayView:
Show Hint

Show Solution


6.12 -- Member selection with pointers and references
Index
6.11 -- Reference variables

69 comments to 6.11a — References and const

  • Minono

    Regarding the code given in the hint:

    What do the curly braces around the function arguments do?

  • Simone

    1.) The size of ArrayView will be usually be 16 bytes on 64 bit systems.
    Not to be pedantic but to check my understanding: on a 64bit system the `const int*` will occupy 64bit/16bytes and the size_t from https://en.cppreference.com/w/cpp/types/size_t since C++11 is at least 16bit/2bytes for a total of 18 bytes, is it wrong?

    2.) Also, from a previous lesson:

    is how we pass arrays without having them decay:
    does this mean that we copy the entire thing or is it equivalent to passing them by reference?
    Is the cost in memory of using this method lower than letting it decay to a pointer and passing a size parameter as well?

    • nascardriver

      1.) A pointer is 64 bits (8 bytes), `std::size_t` is 64 bits (8 bytes) too. 8 bytes + 8 bytes = 16 bytes

      2.) No elements get copied. The cost is the same as passing a pointer (without the length). The cost is cheaper than passing a pointer AND a length (Because by using a reference the length is a compile-time value that's baked into the function and doesn't have to be obtained at run-time).

  • Test

    I was wondering why I got compile-error on the first code but not on the second code!

    Code #1

    Code #2

    • sulfur93

      There are 2 things. First, I feel like the reason could be prefix and postfix usage. Second, your functions have a return type (int) but you have not assigned anything to them.

      In your first code postfix is applied. Your code says that return the x value and increment x ( which is already returned and destroyed value ) by 1. The creates the problem I guess.

      On the other hand your second code says that increment the x value by one and then return the x value.

  • Sonia

    I changed the 'Hint' code a bit.

    I have some questions regarding the following code?

    Why did I got COMPILE errors from lines 15,16 but a RUN-TIME error from the line 17? Why I didn't get compile-time error from line 17 too?

    ERROR MESSAGE (compile errors):
    'length' cannot be modified because it is being accessed through a const object    
    'value' cannot be modified because it is being accessed through a const object    

    RUN-TIME ERROR:
    Run-Time Check Failure #2 - Stack around the variable 'array' was corrupted.

    • nascardriver

      If you can't compile something, you can't run it. I have no idea how you managed to do that.

      • Sonia

        Sorry for the confusion. I commented out lines 16 and 16 and re-compiled and then I got no compile-error but I got run-time error.

      • Sonia

        Please consider the code below:
        I was wondering why lines 15 and 16 gave me compile-time errors (when uncommented) and line 17 won't give me a compile-time error , while i modified the array in line 17 too! but i just got error, " cannot be modified because it is being accessed through a const object" when uncommented lines 15, and 16.

        • nascardriver

          You can't uncomment line 15 and 16, because `array` is const, which means that modifying it is illegal. The elements of `value` however, are not const. To make those const, you'd have to change line 8 to

          Line 17 causes undefined behavior, because `array.value` has 3 elements (It's the array from `main`). The highest index in an array of 3 elements is 2. There is no `array.value[3]`.

  • Alek

    take a look at this snippet pls:

    In third line in the right side of the "=" operator I'm dereferencing the pointer variable c and then assign it to "&add" so why does it work ? int &add isn't  const so in fact this must give an error but why doesn't it so ?

    • Cerezas

      Dereferencing a pointer gives you an l-value.
      The pointer c points to a non-const value, hence *c gives you a non-const l-value:

      The statement "References to non-const values can only be initialized with non-const l-values." in lesson 6.11 stands unstained.

  • Hassan

    "Fundamental data types should be passed by value, unless the function needs to change them."
    "Rule: Pass non-pointer, non-fundamental data type variables (such as structs) by (const) reference."

    After learning this interesting feature of C++, I wonder why is the rule here recommending the use of reference only for non-fundamental data types?

    I thought that the rule of thumb for a passing an argument to a function is using:

    , and:

    unless the data type size of the passed variable is smaller than 4-bytes (or 8-bytes for 64 bits system), to avoid making copies (and for all data types, fundamental and non-fundamental).

    Please correct me if I'm wrong, why should we pass by value in general?

    • nascardriver

      Passing fundamental types (Or types that are leightweight in general, eg. std::string_view or std::initializer_list) by reference is slower than copying them.
      If you pass by reference, the compiler has to add an indirection (ie. create a pointer, pass the pointer, read from the pointer). For large types, this is a lot faster than copying the value. But for leightweight types, the copy of the type has about the same cost as passing the pointer, so you're saving the indirections.

      • Alek

        hey, I am curious about these behind the scenes processes that happen,You say : if you pass by reference, the compiler has to add an indirection (ie. create a pointer, pass the pointer, read from the pointer).
        1:could you tell me how do you know what happens under the hood ?is it from any site or reference or sth that I can also check?
        thanks!

        • nascardriver

          Most of the behind the scenes knowledge I have comes from developing cheats for video games, where one has to have a good understanding on how programs work at a low level. The more you use C++ and the more problems you run into, the more you will learn from reading explanations online.

  • hellmet

    I recently came across an issue. This was the problematic code piece.

    On the other hand, this works in all cases

    The problem-code worked fine in debug mode, but broke miserably in release mode (both compile fine, meaning I am allowed to take the address of an 'const lvalue reference' to an rvalue). I understand that this (the first snippet) is not the ideal way to initialize stuff, but I don't see why optimization (no matter what type) would break the code here. Specifically, the 'Some::Namespace::CreateOBJ' throws an exception depending on the compiler.

    • nascardriver

      If something works in one compilation mode but not the other, you either have undefined behavior or a compiler bug (unlikely).
      Without a full example, I can't tell for certain what's causing the issue.

      What I suspect: `Some::Namespace::StructNameOBJCreateInfo()` creates a new object (Either it's a constructor or a function call, doesn't matter). `setAttributeX` returns a reference to `*this`. The created temporary by the call to `Some::Namespace::StructNameOBJCreateInfo` dies at the end of line 2. A const reference can't perform lifetime extension in this case (Returned references can't be extended).
      In snippet 1, you're trying to perform lifetime extension, but the temporary gets destroyed. Accessing the reference invokes UB.
      In snippet 2, you're copying the temporary before it dies.

      Same issue with `obj_instance`.

      If I'm right, this should fix your issue and avoid copies:

      • hellmet

        > [...] creates a new object [...] setAttributeX` returns a reference to `*this` [...] created temporary by the call to `Some::Namespace::StructNameOBJCreateInfo` dies at the end of line 2.

        I was expecting the whole object to be constructed (everything on the rhs of = to be evaluated completely) and then me holding a reference to it. But.. but why would the ... Ohhh!
        I see! The last .setX() returns a reference and I can't set a const reference to a reference! Holy shit that makes so much sense! On a related note, what about reference collapsing? Does that not come into play here? I read about that here [http://thbecker.net/articles/rvalue_references/section_01.html] in section 8. I can't say I understood everything, I'm still trying to make sense of that.

        > should be a constructor argument really
        The calls are part of the Vulkan API, but yeah, this '.set' pattern seems helpful in cases like this, but I must be careful of the consequences! I'm glad I made the mistake by failing to follow best practices, as it made me appreciate the issue, understand C++ better. Thank you for your on-point explanation!

        I expect this to work though, and it did.

        In both of the above cases (in the code snippet right above), shouldn't the code in '{ thing_here }' 'thing_here' be evaluated completely, with 0 copies since all the .setX() return a reference? With optimizations, I imagine the object is constructed in-place.

        • nascardriver

          I don't think you're there yet

          Output
          --
          ~S()

          Now we add a `setAttribute` call to the temporary

          Output
          ~S()
          --

          The temporary dies before `s` goes out of scope (ie. before you use it for something else). If we try to access `s`, we get UB.
          There are no collapsed references here, that's a template thing.

          > I expect this to work though, and it did
          It does, because the temporary dies after your copied it. But you're creating a copy of the object referenced by the last `setAttribute` call (That is, the temporary object).
          If you create the object first, ie.

          you're getting no copies/moves (Guaranteed) in case 1, and a copy/move/nothing in case 2, depending on the function.

          • hellmet

            Yes, the first part is clear, no extension possible on a returned reference. Thanks for the clarification on the templates thing!

            > copy/move/nothing in case 2
            Hmmm... I see. Need to experiment with some cases then.

            Holy shit, it does make a copy! No moves even (other than explicitly calling std::move)!

            I guess I'll default to writing it out in performance-critical-paths then. In other cases, copying should be okay I guess. Why isn't the object being constructed in-place? I know it's easy for the naïve-me to handwave and say oh this would be nice, but from what compilers are capable of, one would think this is a nice optimization to have?

            • nascardriver

              Your compiler doesn't know what `ConcatString` returns, it might be *this, it might be some other object. The compiler could follow the entire call chain and trace it back to the temporary (if there are no conditional returns), but that's not required. I don't know if this is allowed as an optimization.
              If you use `std::move`, even with full optimizations, you still have a move. I can only recommend again to separate the lines or use likely optimizations:

              Note that the parameter list can be omitted in certain situations, I don't think this is mentioned in the lambda lessons.

              • hellmet

                Yep, it's optimized away, even in debug builds.

                I'll stick to writing it out in hot-code-paths. I'm still wrapping my head around lambdas, but seems like a convoluted syntax for this use case. I'll just write it out, I guess :)
                Thank you for the insight! This was a really enlightening exchange!

  • Charan

    Hey,Are there pointer references as well?Constant pointer references are extremely useful while traversing a linked list.

    • nascardriver

      A const reference to a non-const pointer?

  • Wallace

    This page has two sentences that seem so similar that I'm confused. Either I'm not appreciating the difference or they are redundant.

    In the first section:
    "References to const values are often called 'const references' for short."

    In the second section:
    "A reference to a const is often called a const reference for short, though this does make for some inconsistent nomenclature with pointers."

    Is this an example of a forward declaration of a sentence? ;)

  • helelo

    If I understand, const in functions are mainly used to print variables?

    • No, that's just what's done here. Whenever you pass a variable by reference and don't modify it, you should mark it as `const`.
      If you don't do this, you won't be able to use that function with `const` objects.

  • RyuuGP

    Is there a good reason to assign literal to const reference instead of assign it to actual variable?

  • What is the use of passing structs to the function parameter as const reference if we can do this. ????

    Thanks a lot.

    • This will create of copy of @employee. Copying is slow. References take up a constant space (4 bytes of 32 bit, 8 bytes on 64 bit). Passing by reference is much faster than passing by value if the value has a size that's bigger than 8 bytes or a size that's not representable by 2^n.

    • DecSco

      In addition, say you write a function for giving an employee a raise. If you pass it as a copy, the employee will never get more money, but if you pass it by reference, it works:

      EDIT: ah, saw that you specifically asked for the difference to a const ref. Then this does not apply.

  • Boteomap2

    Hello Alex/nascardriver

    ->References to r-values extend the lifetime of the referenced value
    I Can't see any difference about using reference and non-reference

    • Alex

      You're not using a reference in that example.

      It's kind of silly to do this with a literal -- it's more commonly used with anonymous objects.

  • Chris

    There is so many const positions in c++ that's insane to sum it up:

    int a {5} = open variable
    const int b {5} = const variable
    int *c {&a} = open pointer to open variable
    const int *d {&a} = open pointer to closed variable
    const int const *e {&a} = closed pointer to closed variable
    int &f {a} = works
    int &g {b} = error because reference not const
    const int &h {b} = works because reference now const

    But why does a reference work on a literal like 5 when it's const? There is still no memory address for that isn't it?

    • Alex

      When you initialize a const reference with a literal, the compiler likely implicitly defining an anonymous object to hold value 5 and then setting &ref to point at that.

  • Pikan Ghosh

    Thank you for the wonderful website and thank you in advance for replying my query.

    Scenario 1:

    Output:

    In Function Address: 0x7ffdc9a67d9c
    In Function Address: 0x7ffdc9a67d9c
    Func Address: 0x7ffdc9a67d9c
    Address: 0x7ffdc9a67d9c
    Value: 5

    ==============================
    Scenario 2:

    Output:

    In Function Address: 0x7fffa9df3e9c
    In Function Address: 0x7fffa9df3e9c
    Func Address: 0x7fffa9df3e9c
    Address: 0x7fffa9df3e9c
    Value: 5

    ==================================
    Scenario 3:

    output:

    Func Address: 0x400845
    Address: 0x400845
    Value: Hello

    =========================================

    Scenario 4:

    Output:

    Address: (nil)

    * Uncommenting any of the commented printfs result in segmentation fault.

    Question:
    How can reference or address of local stack variables can be returned in the scenario 1,2 & 3?
    Is there any special treatment of scenario 3, or is it same as scenario 1 & 2?
    What is the difference between each of the scenarios with scenario 4?

    • All scenarios except for 3 cause undefined behavior. Never return references or pointers to local variables or temporaries. They die at the end of the function. Any access after that is undefined. It might work, it might crash.

      > How can reference or address of local stack variables can be returned in the scenario 1,2 & 3?
      You can't.

      > Is there any special treatment of scenario 3
      Yes, C-style strings are special. This is covered in lesson 6.8b (or 6.6).

      > What is the difference between each of the scenarios with scenario 4?
      You got lucky.

      You're writing C. If you know C and are switching to C++ but don't want to read the first couple of chapters, post your code more frequently so someone can point out what to change.

      * <stdio.h> is C, <iostream> and <cstdio> are C++
      * Copy initialization is C, uniform initialization is C++ (Lesson 2.1)
      * printf is C, @std::cout and @std::printf are C++

      • Pikan Ghosh

        Thanks for the clarification. Yes I am switching to C++. The reference thing is very new to me. This is why I was trying different things with it. I will post more codes, once tangled. Thanks again.

  • Pikan Ghosh

    Scenario 1:

    #include <iostream>
    #include <stdio.h>

    int const& retRef() {
        int const &a = 5;
        printf("In Function Address: %p\n", &a);
        return a;
    }

    int main()
    {
        int const& k = retRef();
        printf("Func Address: %p\n", &retRef());
        printf("Address: %p\n", &k);
        printf("Value: %d\n", k);
        return 0;
    }

    Output:

    In Function Address: 0x7ffdc9a67d9c
    In Function Address: 0x7ffdc9a67d9c
    Func Address: 0x7ffdc9a67d9c
    Address: 0x7ffdc9a67d9c
    Value: 5

    ==============================
    Scenario 2:

    #include <iostream>
    #include <stdio.h>

    int const* retRef() {
        int const &a = 5;
        printf("In Function Address: %p\n", &a);
        return &a;
    }

    int main()
    {
        int const* k = retRef();
        printf("Func Address: %p\n", retRef());
        printf("Address: %p\n", k);
        printf("Value: %d\n", *k);
        return 0;
    }

    Output:

    In Function Address: 0x7fffa9df3e9c
    In Function Address: 0x7fffa9df3e9c
    Func Address: 0x7fffa9df3e9c
    Address: 0x7fffa9df3e9c
    Value: 5

    ==================================
    Scenario 3:

    #include <iostream>
    #include <stdio.h>

    char const* retRef() {
        return "Hello";
    }

    int main()
    {
        char const* k = retRef();
        printf("Func Address: %p\n", retRef());
        printf("Address: %p\n", k);
        printf("Value: %s\n", k);
        return 0;
    }

    output:

    Func Address: 0x400845
    Address: 0x400845
    Value: Hello

    =========================================

    Scenario 4:

    #include <iostream>
    #include <stdio.h>

    int const& retRef() {
        return 5;
    }

    int main()
    {
        int const& k = retRef();
        //printf("Func Address: %p\n",&retRef());
        printf("Address: %p\n", &k);
        //printf("Value: %d\n", k);
        return 0;
    }

    Output:

    Address: (nil)

    * Uncommenting any of the commented printfs result in segmentation fault.

    Question: What is the difference between each of the scenarios with scenario 4?

  • Luffy

    Should I use this for arrays, I mean const refernce.

  • Hanin

    Hey, what if I passed a class by const reference and then tried to access to one of its methods that DO NOT MODIFY its state ?
    I tried this because I needed using getters of a certain class I passed by const.ref. and I got an error.
    Does this mean passing a class in this way prevents us from calling its methods ?

    Error : Point.cpp:60:22: error: passing 'const Point' as 'this' argument of 'float Point::getAbscisse()' discards qualifiers [-fpermissive]

    Thanks in advance for your answers and hard work.

    • Hanin

      I think I found the answer. Apparently we have to declare methods that do not alter the class
      as constant methods so that the compiler doesn't get suspicious ..

  • hi alex,
    pls clear my doubt on this

    const int &ref=6;
    cout<<&ref;
       what will be the output ?
       will it print adress of 6
       as 6 is r value.

  • Baschti

    Does ref contain 2 + 3, or 5?

  • Hi Alex / Nascardriver,

    I just threw together something very simple to help me understand the use of references better (in functions):

    This results in the output:

    6
    5
    12

    as expected.  However, does this mean that instead of using globally initialised variables they can be initialised wherever and accessed through an const reference?

    • Hi Nigel!

      You could do so and certainly should in small programs. However, when you're writing bigger projects you'll find yourself with several variables which are used in many places. Passing those around as arguments is tedious, so you'll use global variables or singletons.

  • Matt

    I'm having trouble understanding why this isn't allowed:

    but the following IS allowed:

    Could you maybe clarify why this is the case?

    How is the following code:

    any different from:

    ?
    Thanks for the brilliant website!

    • Hi Matt!

      @ref3 is a reference (alias) to @x, when you modify @ref3, you're modifying @x.

      Assuming this worked,
      @ref3 is a reference (alias) to 6, when you modify @ref3, you're modifying 6. But 6 cannot be modified.

      By saying

      the compiler knows that you're never going to modify @ref3, making the definition legal.

  • Yan

    "Rule: Pass non-pointer, non-fundamental data type variables by (const) reference." - u mean non-fundamental data type variables as what variables? Cuz if int, float, char, bool don't match this category, so what is left? Or u mean non-fundamental data type variables as literals?

  • Orfeas

    Hello Alex,
    Thanks for the wonderful tutorials! I came across these two short programs yesterday but I'm not entirely sure how they work.

    This prints:

    2 5 2
    5 5 5

    Why does the value of variable i change when I point pointer i to variable j's memory adress?

    The next one is rather similar:

    This also prints:

    2 5 2
    5 5 5

    I assume &i is a reference, so does that mean that it doesn't need to be initialized when part of a struct? and then I can't even begin to explain what happens on starting from a.i = j.

    Could you help me understand how they work?

    Thanks in advance!

    • Alex

      On Visual Studio, I don't get 555 for the second line, I get 255. Sounds like maybe a compiler bug?

      • Orfeas

        I read further into the tutorials so I understand pointers and references better, so I understand what happens in program 2, but check this out (I'm also on Visual Studio). Turns out in the first program I actually get an error:

        1>c:usersorfeadocumentsvisual studio 2017projectseurydiceeurydiceeurydice.cpp(18): error C2100: illegal indirection (HERE, LINE 10: A a({ i });)
        1>Done building project "Eurydice.vcxproj" -- FAILED.

        So it was printing the second program instead (as I tried that one first), which still gives me 5 5 5 on the second line.

        I tried to think the flow of the second program through:

        The program starts executing at main: i = 2, j = 5. i is then sent to a (an A struct) so "a.i" _literally is_ i, since &i is a reference variable. Then i (still 2), j (still 5) and a.i (refers to i, so 2) are printed.

        Then I make a.i (literally i) have j's value, so i must have j's value, so cout prints 5 5 5.

        So I then tried figuring out what caused the "Illegal indirection" error in the first program.

        The program starts executing at main: i = 2 and j = 5, just as before. a (an A struct) contains a pointer, to which &i (i's memory adress) is sent.., meaning a.i points to i.
        i (still 2), j (still 5), and *a.i (a dereferenced pointer, pointing to i, so 2).

        Then I make a.i point to &j (right?) so i SHOULD stay the same, j as well, but if I were to derefence *a.i, I'll get 5. So I assume this went fine on your machine and the computer correctly printed 2 5 2, 2 5 5. But mine for some reason found an error and ended up reprinting 2 5 2, 5 5 5 from the previous (here the second) program. What should I do about this?

        • Alex

          Well, your first program has a syntax error, in that you're trying to initialize variable a in a way that the compiler doesn't understand. Try this instead:

          Then your program should compile and run correctly.

      • Kumar Santhanam

        Hi Alex, output is 555 should be second line. because  A a({ i }); it means internally i value alias to &a.i, correct?,  so second time assigning value j value to it will update in i also. so output is 555 for second line.

  • Khang

    Hi, Alex.
    At "References to r-values extent the life time of the referenced value"
    What is the difference between
    [const int &ref=2+3;]
    and
    [const int value=2+3;].
    It seems that they're all extend the life time of "2+3", so is it too obvious
    to say the statement above?

  • FinalDevil

    Hi Alex, at the command

    , should it be

    ? It is reference to rvalue, so we use double ampersand?

    • Alex

      There's no need to double-ampersand here. A double-ampersanded reference variable is still treated as an l-value (after all, it has an address). So essentially, in such a case, the second ampersand is ignored.

Leave a Comment

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