Can't bind "this" to a const object

I'm reading C++ Primer by Lippman, about the this and const member functions. I understand that this is a top-level const pointer that refers to the (non-const) object being invoked:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// This code snippet is something for you to work with, in case you need to use some code
// in your answer. 
#include <iostream>
#include <string>

using std::string;
using std::cout;

struct Foo
{
    Foo(string name) : name(name) {}

    void SetNeighbor(int i, Foo &newNeighbor)
    {
        // "this" has the type: Foo* const 
        this->i = i;
        // The compiler effectively uses: this->neighbor = &newNeighbor
        neighbor = &newNeighbor;
    }

    // Const member function
    Foo* GetNeighbor () const 
    {
        return neighbor;
    } 

    void PrintNeighbor()
    {
        cout << "(" << i << "," << neighbor->name << ")";
    }

private:
    std::string name = "";
    int i = 0;
    Foo* neighbor;
};

int main()
{
    
    Foo f0("Point0"), f1("Point1"), f2("Point2");
    f1.SetNeighbor(100, f2);
    f2.SetNeighbor(50, f0); 

    // Print f1's neighbor's neighbor. 
    f1.GetNeighbor()->PrintNeighbor(); // (50,Point0)
    return 0;
}


I don't understand this statement in the textbook:
Although this is implicit, it follows the normal initialization rules, which means that (by default) we cannot bind this to a const object. This fact, in turn, means that we cannot call an ordinary member function on a const object.


Q1. What is meant by "we can't bind this to a const object"?
Q2. What is meant by "we can't call an ordinary member function on a const object"?

Edited:
A1. When you declare a const object and call that object's member function, that function must be a const member function. If that function isn't a const member function, you'd be trying to pass a const implicit object parameter (this) to a member function that expects a non-const implicit object parameter.

A2. When you declare a const object, you can only invoke that member's const member functions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Foo
{
   void a() const {} // Implicit declaration: void a(const Foo &this) {}
   void b() {}  // Implicit declaration: void b(Foo &this) {}
   // void b() const {} // Overloaded b() with implicit declaration: void b(const Foo &this)
}

int main()
{
   const Foo f1 {};
   f1.a(); // Ok - invokes: a(f1) and the parameter signature matches
   f1.b(); // Error - invokes: b(f1) but f1 is type "const Foo", not "Foo&" as b() expects
           // If you want to do this, you have to define void b() const {}
}
Last edited on
Non-static member functions have an extra parameter called the implicit object parameter. The implicit object parameter is always a reference to the class the function is a member of.

This means there is a close correspondence between member and non-member functions. It's easiest to show by example:
| Non-static member function signature | Corresponding non-member function signature |
| void f()                             | void f(A& this_)                            |
| void f() const                       | void f(A const& this_)                      |
| void g() &                           | void g(A& this_)                            |
| void g() &&                          | void g(A&& this_)                           |
| void g() const&                      | void g(A const& this_)                      |
| void g() const&&                     | void g(A const&& this_)                     |
| void g() const volatile&             | void g(A const volatile& this_)             |
| ...                                  | ...                                         |

Within a member function, keyword this is equivalent to &this_ in the corresponding non-member.

It follows the normal initialization rules

Nearly, the exception is that line 5 compiles below, despite that line 6 is an error:
1
2
3
4
5
6
7
struct A { void f() {}; };
void f(A& this_) {}; 
int main() 
{
  A{}.f(); // okay
  f(A{}); // error: cannot bind non-const lvalue reference of type A& to rvalue of type A
}

The exception probably exists to allow line 5 to compile. Notice that the signatures of A::f and ::f correspond in the table above.

1. What is meant by "we can't bind this to a const object"?
2. What is meant by "we can't call an ordinary member function on a const object"?

It means that given this declaration:
const struct A { void f() {} } a;
The statement
a.f();
Doesn't compile because it tries to pass an object with type const A to a function like void f(A& this_). This would throw away const.

Here's another example of the same:
1
2
3
4
5
6
void f(int& x) { x = 1; }
int main()
{
  const int x = 2;
  f(x); // error
}
Last edited on
Practically, consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>

using std::string;
using std::cout;

struct Foo
{
	Foo(string name) : name(name) {}

	void SetNeighbor(int i, Foo& newNeighbor) {
		// "this" has the type: Foo* const
		this->i = i;
		// The compiler effectively uses: this->neighbor = &newNeighbor
		neighbor = &newNeighbor;
	}

	// Const member function
	Foo* GetNeighbor() {
		return neighbor;
	}

	void PrintNeighbor() {
		cout << "(" << i << "," << neighbor->name << ")";
	}

private:
	std::string name = "";
	int i = 0;
	Foo* neighbor;
};

int main() {

	const Foo f0("Point0"), f1("Point1"), f2("Point2");
	//f1.SetNeighbor(100, f2);
	//f2.SetNeighbor(50, f0);

	// Print f1's neighbor's neighbor.
	f1.GetNeighbor()->PrintNeighbor(); // (50,Point0)
	return 0;
}


which gives a compiler error on L40
'Foo *Foo::GetNeighbor(void)': cannot convert 'this' pointer from 'const Foo' to 'Foo &'

ie object f1 is const but GetNeighbor isn't marked as const (even though it doesn't change the object) and hence the compiler objects as GetNeighbor could try to change the object.

Consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>

using std::string;
using std::cout;

struct Foo
{
	Foo(string name) : name(name) {}

	void SetNeighbor(int i, Foo& newNeighbor) {
		// "this" has the type: Foo* const
		this->i = i;
		// The compiler effectively uses: this->neighbor = &newNeighbor
		neighbor = &newNeighbor;
	}

	// Const member function
	const Foo* GetNeighbor() const {
		return neighbor;
	}

	void PrintNeighbor() const {
		cout << "(" << i << "," << neighbor->name << ")";
	}

private:
	std::string name = "";
	int i = 0;
	Foo* neighbor;
};

int main() {

	const Foo f0("Point0"), f1("Point1"), f2("Point2");
	//f1.SetNeighbor(100, f2);
	//f2.SetNeighbor(50, f0);

	// Print f1's neighbor's neighbor.
	f1.GetNeighbor()->PrintNeighbor(); // (50,Point0)
	return 0;
}


which does compile OK when f1 is const.

Similar to providing a const/nonconst version of operator[], you'd probbaly want to provide a const/non-const version of GetNeighbor - one const and one non-const. The compiler will use the one appropriate to the constness of the object.

1
2
3
4
5
6
7
	const Foo* GetNeighbor() const {
		return neighbor;
	}

	Foo* GetNeighbor(){
		return neighbor;
	}


Functions with the same name/params but differing in being marked const or not are considered different functions.

Last edited on
Thanks for the elucidating post mbozzi, seeplus. I think I understand.

mbozzi wrote:
It means that given this declaration:
const struct A { void f() {} } a;
The statement
a.f();
Doesn't compile because it tries to pass an object with type const A to a function like void f(A& this_). This would throw away const.


The programmer declares a to be const, indicating that its value/state should not be changed, but calling the non-const member f() might modify the state of a because the implicit object parameter passed to f() is non-const. This is fixed by making the implicit object parameter const:

struct A { void f() const {} };

which is equivalent to:

struct A { void f(A const &this) {} };

In other words, a object declared to be const can only call const member functions.
Last edited on
Topic archived. No new replies allowed.