4
class Obj {
public:
    Obj(int aa, int bb): a(aa), b(bb) {}
    Obj(const Obj& o) {a = o.a; b = o.b;std::cout << "copying" << std::endl;}
    Obj(Obj&& o) {a = o.a; b = o.b;std::cout << "moving" << std::endl;}
    int a;
    int b;
};
const Obj& Min(const Obj &o1, const Obj &o2) {
    if (o1.a > o2.a) {
        return o1;
    } else {
        return o2;
    }
}
int main() {
    using namespace std;

    auto o1 = Obj(1,1);
    auto o2 = Obj(2,2);
    auto res = Min(o1, o2);

    cout << res.a << endl;
    res.a = 100;
    cout << o1.a << endl;
    cout << o2.a << endl;

    return 0;
}

The program still prints a word copying which indicates that the copy constructor is activated. Where is the constructor called then ? Why the function does not return a reference of o1 so that modifying the value of res will also changing the value of o1 ?

JFMR
  • 23,265
  • 4
  • 52
  • 76
coin cheung
  • 949
  • 2
  • 10
  • 25
  • 3
    Try `auto& res = Min(o1, o2)`, but don't expect `res.a = 100` to work then, since you're returning `const` reference. – Zereges Oct 15 '18 at 06:10
  • So this means that construction is implemented at assigning the left reference returned by ```Min``` to a non-reference variable ```res``` ? What if I need to use ```auto&& res = Min(...)``` to capture the value, how could I make this work ? – coin cheung Oct 15 '18 at 06:17
  • Anyway, returning a **`const`** reference will prevent the modification of `o1`. – JFMR Oct 15 '18 at 06:26
  • See also `decltype(auto)`, which neither of the answers here mention; that can net a reference without having to write one, so it's better if you were writing code that had to be generic. @coincheung What do you mean "What if"? You just wrote it. But is there a reason you need an rvalue reference there? You'll still get a `const` reference if you are returning a reference to a member of a `const` object. All the generic code in the world can't bypass that, and rightly. – underscore_d Oct 16 '18 at 17:51

2 Answers2

8

The copy is done in the statement:

auto res = Min(o1, o2);

Min() return type is const Obj&. The auto above will be deduced to Obj, not const Obj& (i.e., res type will be Obj). res, which is an object, is initialized by means of the copy constructor (i.e., Obj::Obj(const Obj&)), therefore a copy construction takes place.

If you write instead:

auto& res = Min(o1, o2)

res will be of type const Obj& and no copy construction takes place there, since res will be a reference, not an object.

JFMR
  • 23,265
  • 4
  • 52
  • 76
5

This has to do with how auto deduces the type:

From the CPP working draft (N4713):

10.1.7.4.1 Placeholder type deduction [dcl.type.auto.deduct]
...
4. If the placeholder is the auto type-specifier, the deduced type T' replacing T is determined using the rules for template argument deduction.

And:

17.9.2.1 Deducing template arguments from a function call [temp.deduct.call]
...
2. If P is not a reference type:
...
(2.3) — If A is a cv-qualified type, the top-level cv-qualifiers of A’s type are ignored for type deduction.

  1. If P is a reference type, the type referred to by P is used for type deduction.

So the auto in the below statement

auto res = Min(o1, o2);

deduces res as Obj thus invoking the copy-constructor at the assignment.

So modifying the above to this:

auto& res = Min(o1, o2);

will enable auto to deduce res as const Obj&.

But if you do this, res cannot be modified in main as it is a const reference

P.W
  • 26,289
  • 6
  • 39
  • 76