Search code examples
c++parametersreferenceconstantsconst-reference

Difference between ordinary parameter, reference parameter and const reference parameter passed by ordinary object and object created temporary


Since I'm a beginner in c++, some questions don't quite understand. This question came across by accident while I was reading C++ primer 5th.


I have a Cat class with 3 different constructors(named by Constructor 1, Constructor 2, Constructor 3):

class Cat {
    friend class Cats;

private:
    std::string name;

public:
    Cat() = default;

    // Constructor 1
    Cat(std::string n): name(n) {
        std::printf("Call: Cat(std::string n)\n");
    }

    // Constructor 2
    Cat(std::string& n): name(n) {
        std::printf("Call: Cat(std::string& n)\n");
    }

    // Constructor 3
    Cat(const std::string& n): name(n) {
        std::printf("Call: Cat(const std::string &n)\n");
    }

};

And I want to instantiate the class in two different ways:

class C7T17_Main {
public:
    void run() {

        // Instantiation 1
        Cat cat(std::string("Cathy"));
       
        // Instantiation 2
        std::string s("Mike");
        Cat cat2(s);

    }
};

Then the problem comes:

  • For Constructor 1:

    • Instantiation 1 and Instantiation 2 both work well
  • For Constructor 2:

    • Instantiation 1 raises an error. The compiler complains that 'Candidate constructor not viable: expects an lvalue for 1st argument'
    • Instantiation 2 works normally.
  • For Constructor 3:

    • Instantiation 1 and Instantiation 2 both work well

My guess is:

  • Instantiation 1 does not actually create a variable, but a temporary value for initializing cat, so it is not suitable as a reference parameter.

  • For constructor 3, the const reference parameter represents a constant that can accept a temporary value for initialization


Looking forward to your help. :)


Solution

  • It's pretty simple:

    1. ctor 1 receives the argument by copy (pass by value);
    2. ctor 2 receives the argument by non-const lvalue reference, so it only supports non-const lvalues.
    3. ctor 3 receives argument by const lvalue reference so it supports const-lvalue, non-const lvalue, const rvalue and non-const rvalue.

    Instantiation 1 does not actually create a variable, but a temporary value for initializing cat, so it is not suitable as a reference parameter.

    Yes, it creates a prvalue, and it can be passed by reference like this:

    // ctor 4
    Cat( std::string&& n ) // see the double ampersand
    

    ctor 4 only accepts temporaries (rvalues) by reference.

    For constructor 3, the const reference parameter represents a constant that can accept a temporary value for initialization

    Yes, it can bind to all the values as I mentioned previously.

    For Constructor 2: Instantiation 1 raises an error. The compiler complains that 'Candidate constructor not viable: expects an lvalue for 1st argument'

    This is the expected behavior.

    Important Note:

    Cat(std::string&& n): name(n) { // here n is of type rvalue refernece to string
        std::printf("Call: Cat(std::string& n)\n");
    }
    

    Function parameters that are of type rvalue reference are lvalues themselves. So in the above code, n is a variable and it's an lvalue. Thus it has an identity and can not be moved from (you need to use std::move in order to make it movable).

    Extra note:

    You can even pass an lvalue to a function that only accepts rvalues like this:

    Cat( std::string&& n );
    
    std::string s("Mike");
    Cat cat2( std::move(s) );
    

    std::move performs a simple cast. It casts an lvalue to an xvalue so it becomes usable by a function that only accepts an rvalue.

    Value Category in C++11

    Take a look at this: Value Categories in C++11

    Explanation for the above image

    In C++11, expressions that:

    • have identity and cannot be moved from are called lvalue expressions;
    • have identity and can be moved from are called xvalue expressions;
    • do not have identity and can be moved from are called prvalue ("pure rvalue") expressions;
    • do not have identity and cannot be moved from are not used[6].

    The expressions that have identity are called "glvalue expressions" (glvalue stands for "generalized lvalue"). Both lvalues and xvalues are glvalue expressions.

    The expressions that can be moved from are called "rvalue expressions". Both prvalues and xvalues are rvalue expressions.

    Read more about this topic at value categories.