Open In App

Overloads of the Different References in C++

Last Updated : 10 Nov, 2021
Improve
Improve
Like Article
Like
Save
Share
Report

This article focuses on function/method overloads by references, as well as the types of arguments that can be passed.

Prerequisites:

Overview:
l-value refers to a memory location that identifies an object. r-value refers to the data value that is stored at some address in memory. References in C++ are nothing but the alternative to the already existing variable. They are declared using the ‘&’ before the name of the variable. The rvalue references have been added since the rise of Modern C++ (i.e. since C++11).
As a result, there are now three Call-By-Reference options-

  1. By means of pointers.
  2. By using lvalue (ordinary) references.
  3. By using rvalue references (which were first introduced in C++11).

Only all possible overloads of lvalue and rvalue references would be shown here.

Note: 
For the sake of convenience, std::string is used as an argument in the following examples. Any other type of argument (including user-defined type) can also be used.

Overloads of function taking lvalue reference and rvalue reference-

  1. Non-constant lvalue reference.
  2. Constant lvalue reference.
  3. Non-constant rvalue reference.
  4. Constant rvalue reference.

Non-constant lvalue reference

In this a function accepts a non-const lvalue reference as an argument, this means that one can modify the supplied parameter.

Syntax:

void foo(std::string& str); // non-constant lvalue reference overload

  • The foo() function accepts a non-const lvalue reference as an argument, which implies one can modify (read/write) the supplied parameter.
  • Type of variables/objects that can be passed with respect to the function signature (Refer to below program)-
    1. Only a named modifiable object. (Case 1 in the below program).

Below is the C++ program to implement the above approach-

C++14




// C++ program to implement
// the above approach
  
// for std::cout, std::endl
#include <iostream>
  
// for std::string
#include <string>
  
// for EXIT_SUCCESS
#include <cstdlib>
  
// for std::move()
#include <utility>
  
// Declaration
  
// A foo() function takes the
// argument of non-const lvalue
// reference
void foo(std::string& str);
  
// Driver code
int main()
{
    // Case 1 - A named non-const object
    // non-const object
    std::string namedNonConstObj{
        "This is named non-const object"
    };
    foo(namedNonConstObj);
  
    // Case 2 - A named const object
    // const object
    const std::string namedConstObject{
        "This is named const object"
    };
  
    // Error
    // foo(namedConstObject);
  
    // Case 3 - A unnamed temporary object
    // Error
    // foo(std::string("This is unnamed
    // temporary object"));
  
    // Case 4 - using std::move() for named
    // non-const object
    std::string namedNonConstObjWithMove{
        "This is named non-const object - using std::move()"
    };
  
    // Error
    // foo(std::move(namedNonConstObjWithMove));
  
    // Case 5 - using std::move() for named const object
    const std::string namedConstObjectWithMove{
        "This is named const object - using std::move()"
    };
  
    // foo(std::move(namedConstObjectWithMove));
    // Error
  
    /* Case 6 - using std::move() for unnamed 
  // temporary object */
    // foo(std::move(std::string("This is
    // unnamed temporary object - using
    // std::move()")));
    // Error
    return EXIT_SUCCESS;
}
  
// Definition
void foo(std::string& str)
{
    // do something
    static int counter{ 1 };
    std::cout << counter++ << ". " << str << std::endl;
    // do something
}


Output

1. This is named non-const object

Explanation:
Case 1: Non-const object references can point to non-const objects.
Case 2: Non-const object references can’t point to const objects.
Case 3: It will attempt to utilize move semantics implicitly, which changes the lvalue reference to an rvalue reference, despite the fact that there is no function that accepts an rvalue reference as an input. As a result, copy semantics will be used as a fallback of move semantics. But for copy semantics, a function that accepts a const lvalue reference as an argument is required, which is absent in this case.
Case 4 to 6:  Case 4, 5, and 6 are identical to case 3, with the exception that we explicitly specified to call a function/method that accepts an rvalue reference as an argument by marking the objects with std::move(). 

Constant lvalue Reference

In this, a function accepts a const lvalue argument, which means no modification is possible, one can only read the supplied argument.

void foo(const std::string& str); // constant lvalue reference overload

  • This foo() function takes a const lvalue reference argument, which implies one can only read the supplied argument.
  • Type of variables/objects that can be passed with respect to the function signature (Refer to below program) –
    1. A modifiable named object. (Case 1 in the below program).
    2. A const named object. (Case 2 in the below program).
    3. An (unnamed) temporary object. (Case 3 in the below program).
    4. An object marked with std::move(). (Case 4 to 6 in the below program).

Note: 
There is no need to mark a temporary object with std::move() when a function or method is overloaded with an rvalue reference argument. 

Below is the C++ program to implement the above approach-

C++14




// C++ program to implement
// the above approach
  
// for std::cout, std::endl
#include <iostream>
  
// for std::string
#include <string>
  
// for EXIT_SUCCESS
#include <cstdlib>
  
// for std::move()
#include <utility>
  
// Declaration
  
// A foo() function takes the
// argument of const lvalue reference
void foo(const std::string& str);
  
// Driver code
int main()
{
    // Case 1 - A named non-const object
    std::string namedNonConstObj{
        "This is named non-const object"
    };
  
    // namedNonConstObj will be treated
    // as constant
    foo(namedNonConstObj);
  
    // Case 2 - A named const object
    const std::string namedConstObject{
        "This is named const object"
    };
    foo(namedConstObject);
  
    // Case 3 - A unnamed temporary
    // object
    foo(std::string(
        "This is unnamed temporary object"));
  
    // Case 4 - using std::move() for
    // named non-const object
    std::string namedNonConstObjWithMove{
        "This is named non-const object - using std::move()"
    };
    foo(std::move(namedNonConstObjWithMove));
  
    // Case 5 - using std::move() for
    // named const object
    const std::string namedConstObjWithMove{
        "This is named const object - using std::move()"
    };
    foo(std::move(namedConstObjWithMove));
  
    // Case 6 - using std::move() for
    // unnamed temporary object
    foo(std::move(std::string(
        "This is unnamed temporary object - using std::move()")));
  
    return EXIT_SUCCESS;
}
  
// Definition
void foo(const std::string& str)
{
    // do something
    static int counter{ 1 };
    std::cout << counter++ << ". " << str << std::endl;
    // do something
}


Output

1. This is named non-const object
2. This is named const object
3. This is unnamed temporary object
4. This is named non-const object – using std::move()
5. This is named const object – using std::move()
6. This is unnamed temporary object – using std::move()

Explanation:
Case 1:  Constant object references can point to non-const objects.
Case 2:  Constant object references can point to const objects.
Case 3:  It will attempt to utilize move semantics implicitly, which changes the lvalue reference to an rvalue reference, despite the fact that there is no function that accepts an rvalue reference as an input. As a result, copy semantics will be used as a fallback of move semantics. And for copy semantics, a function that accepts a const lvalue reference as an argument is required, which is present in this case. This is the reason for the successful compilation of the function call. 
Case 4 to 6:  Case 4, 5, and 6 are identical to case 3, with the exception that we explicitly specified to call a function/method that accepts an rvalue reference as an argument by marking the objects with std::move(). 

Non-constant rvalue Reference

In this, a function accepts a non-const rvalue reference, which means one can modify the passes parameter.

void foo(std::string && str); // non-constant rvalue reference overload

  • This foo() function accepts a non-const rvalue reference as an input parameter, which implies you can modify (read/write) the passed parameter. However, rvalue references are utilized as associated with move semantics to steal resources.
  • Type of variables/objects that can be passed with respect to the function signature (Refer to below program)-
    1. A temporary object that doesn’t have a name i.e. unnamed object. (Case 3 in the below program).
    2. An non-const object marked with std::move(). (Case 4 in the below program).

Below is the C++ program to implement the above approach-

C++14




// C++ program to implement
// the above approach
  
// for std::cout, std::endl
#include <iostream>
  
// for std::string
#include <string>
  
// for EXIT_SUCCESS
#include <cstdlib>
  
// for std::move()
#include <utility>
  
// Declaration
  
// A foo() function takes the argument
// of non-const rvalue reference
void foo(std::string&& str);
  
// Driver code
int main()
{
    // Case 1 - A named non-const object
    std::string namedNonConstObj{
        "This is named non-const object"
    };
  
    // foo(namedNonConstObj);
    // Error
  
    // Case 2 - A named const object
    const std::string namedConstObject{
        "This is named const object"
    };
  
    // foo(namedConstObject);
    // Error
  
    // Case 3 - A unnamed temporary object
    foo(std::string(
        "This is unnamed temporary object"));
  
    // Case 4 - using std::move() for
    // named non-const object
    std::string namedNonConstObjWithMove{
        "This is named non-const object - using std::move()"
    };
    foo(std::move(namedNonConstObjWithMove));
  
    // Case 5 - using std::move() for
    // named const object
    const std::string namedConstObjWithMove{
        "This is named const object - using std::move()"
    };
  
    // foo(std::move(namedConstObjWithMove));
    // Error
  
    // Case 6 - using std::move() for
    // unnamed temporary object
    // Use of std::move() with temporary
    // objects is not recommended,
    // if the function with rvalue reference
    // as an argument exist.
    foo(std::move(
        std::string(
            "This is unnamed temporary object - using std::move()")));
  
    return EXIT_SUCCESS;
}
  
// Definition
void foo(std::string&& str)
{
    // do something
    static int counter{ 1 };
    std::cout << counter++ << ". " << str << std::endl;
    // do something
}


Output

1. This is unnamed temporary object
2. This is named non-const object – using std::move()
3. This is unnamed temporary object – using std::move()

Explanation:
Case 1:  non-const lvalue objects cannot be passed to a function that takes non-const rvalue references as an argument (unless an object is marked with std::move()).
Case 2 and 5:  const lvalue objects cannot be passed to a function that takes non-const rvalue references as an argument (even after an object is marked with std::move()).
Case 3:  The compiler will indicate that the function that takes an rvalue reference as an argument should be used. In our example, it does exist. Case 4:  non-const lvalue objects can be passed to a function that takes non-const rvalue references as an argument only if an object is marked with std::move().
Case 6:  Similar to case 3. If a function that takes an rvalue reference as an argument exists, using std::move() with temporary objects is not recommended. 

Constant rvalue Reference:
In this, a function takes a const rvalue reference, which means one can only read the supplied parameter.

void foo(const std::string && str); // constant rvalue reference overload

  • This foo() function takes a const rvalue reference argument, which implies you can only read the passed argument.
  • Type of variables/objects that can be passed with respect to the function signature (Refer to below program) –
    1. A temporary object that doesn’t have a name. (Case 3 in the below program).
    2. A const or non-const object marked with std::move(). (Case 4 and 5 in below program).
  • Because rvalue references are intended for stealing resources, being the const specifier with an rvalue reference to the object contradicts. Stealing the resources of the constant objects is also pointless.

Below is the C++ program to implement the above approach-

C++14




// C++ program to implement
// the above approach
  
// for std::cout, std::endl
#include <iostream>
  
// for std::string
#include <string>
  
// for EXIT_SUCCESS
#include <cstdlib>
  
// for std::move()
#include <utility>
  
// Declaration
  
// A foo() function takes the
// argument of const rvalue reference
void foo(const std::string&& str);
  
// Driver code
int main()
{
    // Case 1 - A named non-const object
    std::string namedNonConstObj{
        "This is named non-const object"
    };
  
    // Error
    // foo(namedNonConstObj);
  
    // Case 2 - A named const object
    const std::string namedConstObject{
        "This is named const object"
    };
  
    // Error
    // foo(namedConstObject);
  
    // Case 3 - A unnamed temporary object
    foo(std::string(
        "This is unnamed temporary object"));
  
    // Case 4 - using std::move() for
    // named non-const object
    std::string namedNonConstObjWithMove{
        "This is named non-const object - using std::move()"
    };
    foo(std::move(namedNonConstObjWithMove));
  
    // Case 5 - using std::move() for
    // named const object
    const std::string namedConstObjWithMove{
        "This is named const object - using std::move()"
    };
    foo(std::move(namedConstObjWithMove));
  
    // Case 6 - using std::move() for
    // unnamed temporary object
    // Use of std::move() with temporary
    // objects is not recommended,
    // if the function with rvalue
    // reference as an argument exist.
    foo(std::move(std::string(
        "This is unnamed temporary object - using std::move()")));
  
    return EXIT_SUCCESS;
}
  
// Definition
void foo(const std::string&& str)
{
    // do something
    static int counter{ 1 };
    std::cout << counter++ << ". " << str << std::endl;
    // do something
}


Output

1. This is unnamed temporary object
2. This is named non-const object – using std::move()
3. This is named const object – using std::move()
4. This is unnamed temporary object – using std::move()

Explanation:
Case 1 and 2: const or non-const named objects cannot be passed to a function that takes a const rvalue references as an argument.
Case 3: Also temporary objects can be passed to the function accepting const rvalue references.
Case 4 and 5: Only if the const or non-const named objects are explicitly indicated with std::move() can they be supplied to a function that takes a const rvalue reference as a parameter.
Case 6: There is no need to mark a temporary object with std::move() when a function is overloaded with const or non-const rvalue reference argument.

Summary:
The kinds of arguments that may be passed to the overloaded function or method with references.

  • Without the use of std::move() while calling a function/method that takes the argument of const or non-const lvalue reference or rvalue reference.

  • With the use of std::move() while calling a function/method that takes the argument of const or non-const lvalue reference and/or rvalue reference.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads