The Dual Usage of const in C++

Const is a keyword originated in the C programming language that had its meaning expanded in C++. The original goal of const in C declarations is to allow for the use of non-modifiable data. The most common example is manipulating string data that is stored in read-only memory. Since it is not possible to modify such data, C has used the const modifier to indicate that these memory areas are immutable.

Later, const has also been used in C++ member functions to express the capacity of objects to apply such functions to a const object. As such, it indicates the ability of objects to behave in non-mutable ways.

First, lets state that there are some advantages in using const whenever possible. The biggest advantage is in making clear the intention of the developer: such member functions are supposed to let the object in the same state. However, as software designers we also need to realize the using const in this way may introduce a few issues.

While this use of const seems just like a logical extension of the original concept, a number of problems may result from this seemingly useful and ingenious generalization. In a nutshell, when considering the possible costs, one starts to realize that the advantages of this feature are not enough to balance the problems arising from using it.

Design Issues

A big concern with const methods is that the const declaration makes each class to behave in two separate ways: a const and a non-const version. This makes it more difficult to reason about the responsibilities of the class, because they can change with different types of use. You may think about this const/non-const split as analogous to the use of ifdef inside code: you need to apply different tests to check that each case of the ifdef is still true.

Similarly, a test case for classes with const member functions has to take in consideration the different behavior arising in each situation. This is especially true because the same member function in a class can be declared differently with one version that is const is another that is non-const.

As bad as it may seem, given the design of C++, if you want to use standard libraries such as the STL then you have no choice. You need to use const whenever required, because the use of const contaminates other parts of your code. If a library expects const and your code doesn’t, you will see frequent compilation problems.

Const as a memory specifier

As mentioned earlier, there are certainly areas in which const is necessary in a language such as C++. The most common place is when constant strings are involved. Since there is no high level type for strings, one has to use const to express that an array of characters shouldn’t be modified. This is the case, for example, when string constants are used.

However, for other types there is little advantage in using const. First of all, const is a storage specification, not a high level description of the class capabilities. Essentially you’re saying that the memory used by the class is in a place where it shouldn’t be modified. Using const for anything else is just an invitation for trouble. For example, there is the strange “const cast” used to remove consents from a pointer. It is clear that this is necessary only because of the non-stadard things people are doing with const. The const-ness of a pointer should not be removed just because we want it, since what will happen when the memory is truly non-modifiable?

Using const then equates to use a low-level memory specification to determine the behavior of a high level entity.

Defining Mutable Classes

Some people believe that const should be used because it can determine if an object is modifiable or not. That is a mistake. Modifiability is a feature of a class, not of a single object. Therefore, we shouldn’t have methods that behave in one way if an object is const and in another otherwise.

Smalltalk solves these issues by creating two separate concepts instead of trying to conflate the two different ideas into the same class. For example, an Array in Smalltalk is immutable. However, you can create a MutableArray to get a version that can be modified. No compiler magic is necessary for this to work, and the decision is much easier for the developer: you choose one of the classes de-pending on what you want to do with the array.

The best thing to do is to clear our minds about if a class is modifiable or not, instead of designing a mix of the two. With C++ const, one needs to frequently evaluate a method and say if it is const or not (both versions can also coexist). This leads to bad de-signs, where what is modifiable is left at the whim of the program-mer when adding the latest member function. A better design would come from deciding if this is a mutable or immutable class. Then make sure that the way to class interacts with others satisfy the contract initially established.

The C++ Role in Industry

A hot debate has been maintained for several years about the advantages or disadvantages of C++ in the practice of programming. C++ has certainly gained a lot of strength in the market, especially since the new standard (C++14) was approved and became supported by most compiler vendors. On the other hand, C++ still receives lots of critics for being a language that is not very accessible to new developers and with lots of rough edges that make it difficult to create code free of known “pitfalls”.

While some of these critics are well deserved, the language has taken a lot of steps in the right direction by providing safer mechanisms for initializing objects, for example. They have also done a good job to reduce verbosity in the type system, though the use of automatic type detection.

Another group of programmers have frequently advocated for the use plain C as the best way to fight the problems in the C++ language. This has been epitomized by the debate between Linus Torvalds and some developers that wished to introduce C++ code into the Linux kernel. This can be seen as a both a movement towards the abandonment of C++ altogether, or just as a way of avoiding the more complex features and using C++ as a “better C”.

Industry versus Single Users

While the pros and cons of C++ provide for a multifaceted debate,  I would like to remark on the differences between C and C ++ in industrial applications. This is a important distinction to make, because the needs of industry are frequently at odds with other uses of the language, such as in an academic scenario, or as a vehicle for personal programming projects.

C++ shines on its extensive support for higher level constructs, most notably classes and templates. At the same time, C language users have been more concerned about the high performance and transparency provided by C syntax. This just reflects the different priorities of these groups of users.

Industrial use of a language normally emphasizes the collaborative aspects of programming. In a way, it is much easier to have dozens of programmers working in a project when each programmer is responsible for a single class, which is contained in its own file with clear boundaries to the rest of the application. While it could be possible to replicate this kind of division of labor with C, it is much more complicated to design a set of programming standards that give the same separation between modules as provided by C++.

At the same time, single programmers see the verbosity of C++ as a deficiency, since it requires a lot of extra work to provide something that is not necessary for their needs. For programmers that work mostly alone, C is the most direct way to design an application because it doesn’t impose verbose boundaries between the different parts of the application. The individual programmer will in most cases have the whole project in his or her own mind, and artificial boundaries will only slow down the process. In a industrial setting, however, different programmers are assigned to maintain their own interfaces, making the whole process much easier to manage.

Moreover, programming as done within companies is every day more reliant on connecting to external libraries and frameworks. This is another situation in which C++, with its standard (even if cumbersome) class model provides a definite advantage over C. Suddenly programmers are able to create and manage objects provided by other groups or companies, using the same mechanisms for creating, and releasing objects.  C programmers normally have a more difficult time because each library has it own mechanics for managing data objects, which may not match properly with the style of the current code.

In summary, the needs of individual users are frequently at odds with the needs of corporations. Given these differences, it is not surprising that some users prefer C to C++, while C++ has become so successful in industry. This is also a reason why many open source projects prefer C instead of C++. While collaborative in nature, open source developers are more used to work alone in their projects, and combining their contributions into mainstream. This is a very different mindset from programming as performed in companies, and in more corporate-like projects such as Mozilla for example.

Avoiding Lengthy C++ Template Instantiations

C++ templates are a powerful mechanism that can be used to create generic code. With templates, it is also possible to remove undesirable code duplication, since the same code can then be applied to data of different types.

On the flip-side, however, templates can also create problems due to the potential they have to slow down compilation times. Because all the code in a template is generally available to the compiler when processing translation units, it is difficult to provide separate compilation for templates. An example of library that is victim of this behavior is boost, where typically all the functionality is included in header files. These header files are then included each time the library is referenced in an implementation file, resulting in long build times.

Despite these shortcomings, in some situations it is possible to reduce the amount of work done by the compiler in behalf of templates. This article shows a simple technique that can be used to achieve faster template compilation speeds in the particular case in which desired instantiations are known ahead of time.

Pre-Instantiating Templates

Certain templates are known to be used in only a reduced number of cases. For example, consider a numeric library that creates code for different floating point types. Each class in the library can be instantiated with a particular floating point type, such as double, long double, or float. Consider for instance the following definition:

// file mathop.h
template <class T>
class MathOperations {
public:
 static T squared(T value) {
 return value * value;
 }
// ...
};

This class can be used in the following way:

#include <mathop.h>
MathOperations<double> mathOps;
double value = 2.5;
cout << "result: " << mathOps.squared(2.5) << endl;

Unfortunately, because the class MathOperations is a template class, we have to include its complete definition as part of the header file, where it can be found by the compiler whenever the class is instantiated.

A possible way to reduce the size of the header file is to pre-instantiate the template for the types that we known in advance.

The first step is to remove the implementation from the header file. This is clearly possible, since you can implement class member functions outside the class declaration (if the class is a template or not). Then, you need to add the implementation to a separate source file. Once this step is done, client code will be able to use the template class interface, but will not be able to generate code. Therefore, for this to work, you need to instantiate the templates on the implementation file.

// file mathop.h
template <class T>
class MathOperations {
public:
 static T squared(T value);
 // ...
};
// file mathop.cpp
// template member function definition
template <class T>
T MathOperations<T>::squared(T value) {
 return value * value;
}
void instantiateMathOps() {
 double d = MathOperations<double>::squared(2.0);
 float f = MathOperations<float>::squared(2.0);
 int i = MathOperations<int>::squared(2);
 long l = MathOperations<long>::squared(2);
 char c = MathOperations<char>::squared(2);
}

In the example above, I chose to instantiate five versions of the original template for numeric types. The main limitation of this technique, as I mentioned above, is that your clients will not be able to generate templates for the additional types they may want to use. However, in a few situations you may really want to restrict how these templates are used, and the technique above works as desired.