Debugging – An Easy Way to Catch Typing Errors in C++

A common bug in C++ programming is mixing different data types together – generally, this is inadvertent.

I developed this technique back in 1997 or 1998 after my recommendation that all types be made classes was shot down.

It requires your programming style does not use the fundamental C++ data types directly, but instead you rename them using a typedef.

You follow good programming practice and don’t use fundamental C++ types directly, but hide them behind a typedef. Unfortunately, typedef only gives it a new name, but does not create a new type.

typedef float TEMPERATURE_C;
typedef float TEMPERATURE_F;
typedef float WIND_SPEED_MPH;
typedef float WIND_SPEED_KMH;
typedef float BAROMETRIC_PRESSURE_KPA;
typedef float BAROMETRIC_PRESSURE_MILLIBARS;

While it makes for code that is more self documenting:

TEMPERATURE_C GetCurrentTemperature(void)
{
    return g_current_temperature;
}

it does not change the fact that all the new types are really float and that is how the compiler sees them. Because the compiler sees them all as float, you could not create this function returning a “different” type:

TEMPERATURE_F GetCurrentTemperature(void)
{
    Return g_current_temperature * 9 / 5 + 32;
}

because the compiler will complain that function GetCurrentTemperature has already been defined.

You can also mix and match those newly defined types without complaint from the compiler because they are all float.

TEMPERATURE_C c = 25.3;
TEMPERATURE_F f = 78.2;
WIND_SPEED_MPH s;
s = c + f;

While this example is an “obvious” wrong use and abuse of the declared types, this kind of mixing can still happen.

If you are developing software for a weather monitoring application, you likely have types for temperature (both °F and °C) wind speed (both MPH and km/h – maybe even feet per second and meters per second), barometric pressure (in a variety of units) and others.

To calculate the average temperature over a period of time, it would be wrong to average both °F and °C temperatures together. Perhaps there is a bug in the code that inadvertently adds a °C value into the average calculation for °F – maybe the function GetCurrentTemp() returns the value in °C. It is unlikely to cause a serious discrepancy – something you can put down to rounding errors or inherent floating point inaccuracy (maybe blame Intel for a floating point bug in their chip).

The compiler certainly won’t warn you about mixing types.

This is the sort of bug that most programmers would say can only be caught by careful code inspection. But they would be wrong because the compiler can catch these errors for you.

Let the Compiler Catch Type Mismatches for You

If we replace the fundamental C++ type with a non-fundamental type, a type that C++ knows nothing about, for which it does not define any implicit conversions for you, then the compiler can catch type mismatch errors for you.

The following class meets most needs for a quick and dirty type replacement – it defines the basic arithmetic operations and comparisons that are possible on fundamental types (you could extend it to include the bitwise operators and % operator):

class  dummy
{
public:
    dummy operator+ (const dummy s) {return *this;};
    dummy operator- (const dummy s) {return *this;};
    dummy operator* (const dummy s) {return *this;};
    dummy operator/ (const dummy s) {return *this;};
    dummy operator+= (const dummy s) {return *this;};
    dummy operator-= (const dummy s) {return *this;};
    dummy operator*= (const dummy s) {return *this;};
    dummy operator/= (const dummy s) {return *this;};
    bool operator< (const dummy s) {return false;};
    bool operator> (const dummy s) {return false;};
    bool operator<= (const dummy s) {return false;};
    bool operator>= (const dummy s) {return false;};
    bool operator== (const dummy s) {return false;};
    bool operator!= (const dummy s) {return false;};
};

Note: This class is non-functional, its sole purpose is to help in finding typing errors at compile time. Once all errors are found and corrected, you would restore the original typedef

Because we were good programmers and used typedef to rename the fundamental C++ types, we simply replace an existing typedef with our new type:

// typedef float TEMPERATURE_C;
typedef dummy TEMPERATURE_C;

and then compile our code.

Why Your Compile Will Fail

It is unlikely your code will successfully compile. There are 5 reasons for this:

1) this class is incomplete: harmless variable initializations will fail because the class doesn’t know how to handle them:

TEMPERATURE_C temp = 37.6;

2) you are calling library functions that don’t know how to use this new type

TEMPERATURE_C temp;
scanf("Enter a temperature in Celsius %f\n", &temp);

3) you are using it in a switch statement:

// typedef int AGE;
typdef dummy AGE;
.
.
.
AGE age;
.
.
.
    switch (age)
{
        case 1:
        .
        .
        .
}

4) this class is incomplete: legitimate arithmetic operations will fail (like conversions, scaling, averaging, etc):

TEMPERATURE_C temp_c;
TEMPERATURE_F temp_f;
.
.
.
temp_f = temp_c * 9 / 5 + 32;

5) because you have mismatched types in: assignments, operation, or function calls:

TEMPERATURE_C temp_c;
TEMPERATURE_F temp_f;
TEMPERATURE_F Celsius_To_Fahrenheit(TEMPERATURE_C c);
.
.
.
// meaningless assignment, should be a conversion
temp_f = temp_c;
.
.
.
// incorrect type in call to function,
// incorrect assignment of result

temp_c = Celsius_To_Fahrenheit(temp_f);
.
.
.
// meaningless addition
temp_f = temp_f + temp_c;

Review Each Error

It’s not fun, but you have to review each error and decide whether it is a valid one or a spurious one.

However, it beats looking through all the code to try and find all uses of the type. At least the compiler is letting you know where that type is being used and possibly misused (places where the type is unambiguously being used correctly will not appear as errors or warnings).