Previous Table of Contents Next


8.2.3.2. Constant Pointers and Pointers to Constants

Now let’s see how const and * interact. First, here’s a common case:

   const int* cp;

By looking for the first non-type, we see that the specifiers are const and int and the declarator is *cp. That means that we should treat const int as a unit, as if const modifies int. Viewed that way, it should be easy to see that cp is a pointer to a constant integer object. Moreover, changing the declaration to

   int const* cp;

does not affect its meaning because the specifiers may be reordered freely.

If we want to say that the pointer itself is constant, we must move the const into the declarator:

   int *const pc = &i;

and indeed this declaration requires an initializer because we won’t be able to give pc a value later. Again applying the rule, we see that the * marks the beginning of the declarator, which means that the const is now part of the declarator and not part of the specifiers. Here is an example to emphasize this distinction:

   int i, *const pc = &i;

This declaration has two declarators, namely i and *const pc.

Note that our rule implies that if a declaration has a * in it, that * is never part of the specifiers. The * always says that something is a pointer; to say that the pointer is a constant pointer, we always follow the * by const. In other words, to declare a constant pointer directly, look for a place to say *const.

Locating such a place can be confusing because it is possible to use typedef to define pointer types that conceal the *:

   typedef int* IP;

In this declaration, the specifiers are typedef and int and the declarator is *IP. Once we have it, we can say

   const IP p = &i;

which declares a pointer without using a *. What is the type of p?

Applying our rule again, we find that the specifiers are const IP (because IP is a type name) and the declarator is p. Therefore, it appears that the const modifies IP and p is therefore a constant pointer. Indeed, that is exactly what happens: It is as if we had written

   int* const p = &i;

Indeed, it might be clearer if we had written

   IP const p = &i;

To confirm your understanding of where the declarator begins, note that in

   int* const p = &i;

the declarator is * const p, but in

   IP const p = &i;

the declarator is just p.

From the foregoing examples, we learn that in the following example, the types of cp and pc are different:

   const IP pc = &i;                     // constant pointer to int
   const int* cp = &i;                   // pointer to constant int

because pc is a constant pointer and cp points to a constant. It is very important that you understand this distinction; if you don’t, you might want to read this section again until you do.

8.2.3.3. Adding Member Functions and Pointers to Members

All the properties of const that we have considered so far are part of the syntax that C adopted from C++. In addition, C++ has the notions of member functions and pointers to members. Fortunately, the way C++ handles these notions fits well with how C does it, so once you’ve mastered the complexities of C declarations, the C++ rules aren’t much harder. Accordingly, the following discussion does not describe a single, obvious source of misconceptions. Rather, the interactions between const, type declarations, and pointers to members are rich enough, and can occur in enough contexts, that the rules may not be easy to grasp at first. The main thing to remember while reading the discussion that follows is that these rules exist and can be looked up as needed.

Recall that classes may have constant member functions, which promise not to change the value of their objects:

   class Thing {
   public:
        int get() const;
        void put (int);
};

Here we’ve said that class Thing has two member functions called get and put. If t is a Thing, then calling t.put(42) might change the value of t, but calling t.get() does not.

Notice that when we declare a constant member function, the const goes at the end of the declarator. That now gives us three possible places for const to go, so that

   class X {
   public:
        const int* const f() const;
   };

declares a class with a member function called f that returns a pointer to a constant integer (the first const) that cannot itself be modified (the second const). Moreover, the function promises not to modify its object (the third const).

Recall that we can declare pointers to class members:

   void (Thing::*pp)(int) = &Thing::put;

declares pp as a variable that can potentially contain a pointer to any member function of class Thing that accepts an int argument and returns void. Of course, there happens to be only one such function in this case, namely Thing::put.

Suppose we want to declare a member pointer that could point to Thing::get. It might seem obvious that we could do it this way:

   int (Thing::*gp)() = &Thing::get;     // error

but when we try it, we find that it doesn’t work. The reason is that we’re allowed to call get only on behalf of a const object:

   const Thing ct;
   int i = ct.get();

but if we try to do the same with our member pointer, the compiler stops us:

   const Thing ct;
   int i = (ct.*gp)();                    // error

Although get promises not to change its object, that promise is not carried along into gp. After all, gp could potentially point to some other member of Thing that might change its object, in which case evaluating (ct.*gp)() changes ct even though ct is not allowed to change. We therefore need some way of declaring a pointer that is like gp except that it must be restricted to pointing only at constant member functions.

The way to do that follows the form of the member function declaration itself:

   int (Thing::*cgp)() const = &Thing::get;

Now we can say, for example

   const Thing ct;
   int i = (ct.*cgp)();

with no problems.


Previous Table of Contents Next