4

So earlier on today, I was catching up with good old C++ and when I was compiling my code it was not working. Like a few programmers, I started hacking about, and eventually found that adding the keyboard const cured the problem. However, I don't like hacking about a lot and want to find out why the code was working fine after adding the const.

This was my code BEFORE adding the const to the constructor:

#include <iostream>
#include <string>

using namespace std;

class Names {
private:
    string _name;
    string _surname;

public:
    Names(string &name, string &surname) : _name(name), _surname(surname) {}

    string getName() const {return _name;}
    string getSurname() const {return _surname;}
};

int main(){
    Names names("Mike", "Man");

    cout << names.getName() << endl;
    cout << names.getSurname() << endl;
}

I was getting these errors:

names.cc:19:27: error: no matching function for call to ‘Names::Names(const char [5], const char [4])’
  Names names("Mike", "Man");
                           ^
names.cc:19:27: note: candidates are:
names.cc:11:2: note: Names::Names(std::string&, std::string&)
  Names(string &name, string &surname) : _name(name), _surname(surname) {}
  ^
names.cc:11:2: note:   no known conversion for argument 1 from ‘const char [5]’ to ‘std::string& {aka std::basic_string<char>&}’
names.cc:5:7: note: Names::Names(const Names&)
 class Names {
       ^
names.cc:5:7: note:   candidate expects 1 argument, 2 provided
<builtin>: recipe for target 'names' failed
make: *** [names] Error 1

However, after adding the const keyword within the constructor Names(string const &name, string const &surname) : _name(name), _surname(surname) {} -- it seems to be working.

This is my working code:

#include <iostream>
#include <string>
using namespace std;

class Names {
private:
    string _name;
    string _surname;

public:
    Names(string const &name, string const &surname) : _name(name), _surname(surname) {}

    string getName() const {return _name;}
    string getSurname() const {return _surname;}

};

int main(){
    Names names("Mike", "Man");
    cout << names.getName() << endl;
    cout << names.getSurname() << endl;
}

Now, a few questions:

  1. Why was the code not working without the const for pass by reference? Is it good practice to always pass by reference in your constructors, and if so does it mean we have to use the const keyword?
  2. So if I passed by value in the constructor say: Names(string name, string surname) : _name(name), _surname(surname) {} does this mean that _name and _surname are null or are they the values passed. I know in pass by value, a copy of the variable is being made and changes are being made to the copy. But when does the copy get destoryed or out of scope? It is a bit confusing.

Thanks

p.i.g.
  • 2,815
  • 2
  • 24
  • 41

3 Answers3

14

A string literal has to be converted to a std::string, which would be a temporary, and you cannot pass a temporary by non-const reference.

Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
  • Thank you for your response. I am still confused by it all. Could you please give me an example? –  Nov 09 '15 at 13:13
  • 3
    @CodeMan: Your code is the example. Is the problem that you did not realise `"a string literal like this"` is _not_ a `std::string` object? And/or that an implicit conversion results in a temporary? And/or that a temporary may not be bound to a ref-to-non-`const`? These are three separate considerations, all answered previously on SO. Though I agree this answer needs fleshing out. – Lightness Races in Orbit Nov 09 '15 at 13:33
  • @LightnessRacesinOrbit I considered having a class with life-cycle instrumentation in an online-compiler's store generally useful and tried to "flesh" the *temporary* part "out" based on that instrumented class. – decltype_auto Nov 09 '15 at 15:29
2

It's quite difficult to add something to @CoryKramer's explanation

A string literal has to be converted to a std::string, which would be a temporary, and you cannot pass a temporary by non-const reference.

that would not just rephrase it.

Anyway, here's some instrumented code you (@CodeMan) could play with

#include <iostream>

std::ostream& logger = std::clog;

struct A {
    A() = delete;
    A(int ii) : i(ii) {logger << "A(int) ctor\n";}
    A(const A& a) : i(a.i) {logger << "A copy ctor\n";}
    A& operator= (const A& a) { i = a.i; logger << "A copy assigment\n"; return *this;};
    // nothing to steal here
    A(A&& a) : i(a.i) {logger << "A move ctor\n";}
    A& operator= (A&& a) {i = a.i; logger << "A move assigment\n"; return *this;};
    ~A() {logger << "A dtor\n";}

    int i;
};

void foobar(const A& a) {
    logger << "foobaring const A&\n";
}

void foobar(A& a) {
    logger << "foobaring A&\n";
}


int main(){
  int i(42);  
  A a(i);
  logger << "ctored a\n===================\n";  
  foobar(a);
  logger << "foobared a\n-------------------\n";  
  foobar(i);
  logger << "foobared " << i << "\n===================" << std::endl;
}

live at Coliru's.

As you can see from the output

[...]
===================
foobaring A&
foobared a
-------------------
A(int) ctor
foobaring const A&
A dtor
foobared 42
===================
[...]

in the 2nd foobar invocation there's implicitly a temporary A ctored from that int argument, and that temporary A instance is passed into a different, namely the const version of foobar, thus as a const A &.

And you can also also that, right after that 2nd foobar returned, that temporary A is dtored.

decltype_auto
  • 1,706
  • 10
  • 19
0

1- "Mike" and "Man" in your call to the constructor are temporaries and non-const references cannot bind to temporaries. Passing built-in types by value is more efficient and all other objects by const reference.

2- If you pass by value _name and _surname will have the passed values. The passed parameters copies are local to the function so they get destroyed when the constructor goes out of scope.

Mustafa
  • 1,814
  • 3
  • 17
  • 25