Open In App

Overloads of the Different References in C++

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

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




// 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

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++ 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

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




// 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

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




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


Article Tags :