Skip to content
Nathan Crookston edited this page Aug 15, 2014 · 1 revision

Question 1:

Why can an int& be passed into a function expecting const int&?

void foo(const int& i);
...
int a = 42;
int& b = a;
foo(b);

Answer 1:

Non-const references and pointers can be implicitly converted to const references and const pointers. The converse is not true -- const references cannot be implicitly converted to non-const references. Intuitively it makes sense that we can choose to treat a mutable value as read-only, but we cannot do the opposite.


Question 2:

Consider the following:

//A function which casts away constness and prints the modified argument.
void q1(const int& i)
{
  int& rI = const_cast<int&>(i);
  rI = 5;
}
...
int c = 42;
q1(c);
std::cout << c << std::endl;// Prints 5
const int d = 43;
q1(d);
std::cout << d << std::endl;// Prints ???

Casting away constness works when a reference to a non-const int is implicitly converted to a reference to a const int. Does it work if that reference is to an actual const int?

Answer 2:

No, it doesn't. Doing so invokes undefined behavior, which means it could do just about anything. On VC10, it just changed the reported value in q1, leaving the original value unchanged. Other compilers may crash or perform even more surprising behavior. The takeaway is that const_cast cannot be used indiscriminately. There are very few situations where it should be used [^1].


Question 3:

How would the declaration and implementation of q1 need to change to cause a compile error in the case highlighted by question 2?

Answer 3:

If q1 is dead-set on modifying the referenced argument to be a new value, then that argument should be passed by non-const reference. const references cannot be implicitly converted to non-const references, and so the compiler will alert us when that is attempted.

void q1(int&)

Question 4:

Of the following pointer declarations, which can be used to modify the pointed-to value? Which can be modified to point to a different value?

int i = 42;
const int* pCI = &i;
int* const cpI = &i;

Answer 4:

cpI, the const pointer to an int, can be used to modify the pointed-to int, but cannot be changed to point to another int. pCI cannot be used to modify the pointed-to int, but can be changed to point to another int. Expanding on this, const int* const cpCI; cannot be changed to point to another int, nor can it modify the pointed-to int. Likewise, int* pI; can modify both. A final common point of confusion: int const* p; is a legal pointer declaration. Is it a const pointer, or a pointer to const? If you read the type from right to left, it's clear that it's a pointer to a const int, equivalent to const int* p. (const int& and int const& are likewise equivalent.) Remember that the const can go on either side of the type declaration without changing the type — changing which side of the * it is will change its type.


Question 5:

For the given class:

class Foo
{
  void a();
  void b() const;
  void c() const;
  void d();
  void e();
  int i;
};

Which of Foo::a and Foo::b can be used to modify i?

Answer 5:

Foo::b cannot modify i because Foo::b is a const member function. a, without that trailing const, can be used to modify it. const member functions are used to observe class state, while non-const functions are used to modify it. An example of the former is std::vector's size function — it reports how many elements are present. On the other hand, std::vector's resize function changes what size reports, and so is non-const.


Question 6:

What is the type of this in a const member function? How does it differ from the type of this in a non-const member function?

Answer 6:

The type of this in a const member function of Foo is const Foo* [^2]. The type of this in a non-const member function is Foo*. Since access to i from a member function is equivalent to writing this->i, the constness of this prevents modifying i.


Question 7:

Which functions can be called from Foo::a? Which can be called from Foo::b? ####Answer 7: Foo::a, Foo::b, Foo::c, Foo::d, Foo::e and Foo::printI may be called from Foo::a. Foo::b, Foo::c and Foo::printI may be called from Foo::b. Recursive calls without a guard are still verboten.


Question 8:

Why can't non-const functions be called from const functions?

Answer 8:

Logically, if a non-const function can modify the state of an object and a const fuction cannot, the guarantee of a const function would be violated if it could call a function that modifies state.


Question 9:

Does auto deduce the const or & parts of a return type?

Answer 9:

No, it doesn't. For a type like const int&, it will be deduced as int.


Notes:

  • [^1] Effective C++ code reuse, const-correct interfaces

  • [^2] It's not really a const pointer, despite what I stated in the exercise. The requirements on this do prevent it from being changed to point to something else, however. From the standard:

    9.3.2 The this pointer

    1 In the body of a nonstatic (9.3) member function, the keyword this is a non-lvalue expression whose value is the address of the object for which the function is called.

The standardese is a little opaque, but it essentially means you can't assign to this, nor can you write: Foo** p = &this; For more information on rvalues and lvalues, I've always enjoyed this Stephan T. Lavavej article.

Further Reading: Effective C++ Items 3 and 20, this recent Scott Meyers article.

Clone this wiki locally