Olaf
Olaf

Reputation: 11

What is the point of returning a const alias from a function

Please look at the following code and help me understand: Why the functionality to return a const alias to a literal like my f2 function exists. I don't understand what the point is. The difference between f2 and f3 is that const does allow me to put a literal in the return statement, but again why?

Any help in understanding this is appreciated.

#include <iostream>

const int f1(int a)
{
    return 15;
}

const int& f2(int a)
{
    return 14;
}

int& f3(int a)
{
    a = 12;
    return a;
}

int main()
{

    auto a{ 10 };
    auto b = f1(a);
    auto c = f2(a);
    auto d = f3(a);

    std::cout << a << " " << b << " " << c << " " << d << std::endl;
    a = 1;
    b = 2;
    c = 3;
    d = 4;
    std::cout << a << " " << b << " " << c << " " << d << std::endl;
}

Upvotes: 1

Views: 170

Answers (3)

super
super

Reputation: 12928

Both f2 and f3 have undefined behaviour. You are returning references to local variables. Those local variables are destroyed when the function ends, and the reference is dangling.

The difference between a const reference, and a non-const reference is that a const reference can bind to both rvalues and lvalues.

For non-const references you have to distinguish between lvalue-reference(int&) and rvalue-reference(int&&).

So using the function signature int&& f2(int a) would also compile, but equally have undefined behaviour.

The main reason this is usefull is because when we pass a reference to a function, the function signature tell us if we are expecting an lvalue or an rvalue. We can also overload both and decide to move/copy depending on what we get.

In the case where we don't care, or if we only want to read from the value we can use a const reference and be able to accept both lvalues and rvalues that are passed in.

void foo(MyClass& mc) {
    // We know mc is an lvalue.
    // We could copy mc, or modify it if we want to use it as an output parameter.
}

void foo(MyClass&& mc) {
    // We know mc is an rvalue.
    // We know it would be safe to move from mc in this case.
}

MyClass mc;
foo(mc); // Callsthe first overload
foo(MyClass{}); // Calls the second overload

// The two functions above can be overloaded, so we can make sure we deal
// with both cases in the right way

void foo2(const MyClass& mc) {
    // This can be both an rvalue or lvalue.
    // We don't really care since the reference
    // is const we are only going to read from it.
}

foo2(mc); // Both calls work
foo2(MyClass{});

Upvotes: 1

Adi Peled
Adi Peled

Reputation: 28

Let's say you write a function that needs to return a complex object, but this object shouldn't be modified (such as pointer to a shared resource, class-property, some sort a singleton data and so on). For the sake of this answer, lets assume the type in "struct Point". You have 2 options to do so:

  1. return it by value, which will create a deep copy of its primitive type members and a shallow copy of its by-reference-types members: const struct Point f2(...)
  2. return it by reference, which will copy only the pointer to the object: const struct Point* f2() const struct Point& f2()

both are valid, while the second one has the advantage when dealing with heavy objects.

In the code you provided you do not see the difference because "int" is a primitive type which means it has known way to be copied. This means var "c" isn't actually an alias nor a const, its an int who took its value from the return type of f2

Upvotes: 0

olepinto
olepinto

Reputation: 399

The b, c and d variables in main are initialized with a copy of what the functions return. No matter if they return a copy, a ref or a const ref.

To keep the attributes of the returned value, let's change the first lines in main:


int main()
{
    auto a{ 10 };
    auto& b = f1(a); // Does not compile, a ref can't be tied to a r-value
    auto& c = f2(a); // Ok, c's type is 'const int&'
    auto& d = f3(a); // Ok, d's type is 'int&'

    std::cout << a << " " << b << " " << c << " " << d << std::endl;
    a = 1;
    b = 2;
    c = 3; // Does not compile. c is a ref to a const
    d = 4;
    std::cout << a << " " << b << " " << c << " " << d << std::endl;
}

So, the point is you can return a reference to an internal variable, but now allowing the caller to change it. Doing so (instead of returning a copy), you

  • avoid the copy
  • allow the caller to see any later change

Not much sense for the code above, but think of a method inside a class, where the internal variable can be changed in other ways.

Besides the return type, f2 and f3 are not correct, as they return a reference to a not-in-memory (f2) or temporary object (f3).

Upvotes: 0

Related Questions