To default, or not to default, that is the question...

One of the "interesting" features of C++ is the ability to default the value of parameters to a method.

It's one of those features that I'm sure that Bjarne Stroustrup thought was just flat-out neat (there are a number of feature in C++ that fall into the broad category of "convenience features", I'm pretty sure that defaulted parameters is one of them).

One of the developers (Nick) in my group was working on a fix for a bug, the fix required that he add a new parameter to a relatively common routine.

It turned out that for all of the calls to that routine, except for one, the new parameter would have the same value.

Nick's suggestion was to add the new parameter as a defaulted parameter because that way he wouldn't have to change that much code.

Eventually I vetoed the idea, because of the following problem.

 

Let's say you have a function foo:

HRESULT Foo(DWORD param1, BOOL param2=FALSE)
{
    :
    :
}

Everything's great - your code's running, and it's all great.

What happens six months later when you need to change the signature for Foo to:

HRESULT Foo(DWORD param1, void *newParam, BOOL param2=FALSE)
{
    :
    :
}

Well, in this case, you're in luck, you can simply compile the code and the compiler will happily tell you every function that needs to change.

On the other hand, what if the change was:

HRESULT Foo(DWORD param1, DWORD newParam, BOOL param2=FALSE)
{
    :
    :
}

Now that's a horse of a different color.  The problem is that the types for BOOL and DWORD are compatible.  It means that any code that specified a value for param2, like:

    hr = Foo(32, TRUE);

is still going to compile without error.  The compiler will simply interpret it as:

    hr = Foo(param1=32, newParam=1, param2=FALSE);

Now the language lawyer's are going to shout up and down that this is a design problem in Windows, the BOOL and DWORD types shouldn't have both been defined as "unsigned long", that instead param2 should have been defined as "bool".

The problem is that you STILL have problems.  If param2 was defined as 'bool', what happens if you need to add a non default parameter that's of type 'bool'?  You're back where you were before.

Or you could have:

HRESULT Foo(DWORD param1, int newParam, short param2=3)
{
    :
    :
}

In this case, the automatic promotion rules will quite happily promote a short to an int without a warning.

There have been dozens of times when I've discovered bugs that were introduced by essentially this pattern - someone added a parameter to a function that has defaulted parameters, there was an automatic conversion between the defaulted parameter and the newly added parameter, and what was a simple change all of a sudden became a bug.