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

204 views Asked by At

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. :)

1

There are 1 answers

0
digito_evo On BEST ANSWER

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.