Refactoring with =delete

Previously on this blog:

Pro tip: I’ve found =delete quite a useful tool when refactoring overly complicated overload sets in legacy code. Suppose we have an overload set in some legacy code that looks like this:

void foo(const char*, int, bool = false);  // #1
void foo(const char*, bool = false);       // #2
void foo(int, bool = false);               // #3

We suspect that overload #1 is unused. We comment it out, and the codebase still compiles, so we think it’s safe to remove.

But by removing it we introduced a bug! One of our other files has a call-site like this:

foo("xyzzy", 42);

This used to call #1 as the best match; but with #1 eliminated, it will silently call #2 instead. The “right” way to test whether #1 is unused is not to comment it out, but to mark it =delete. This makes all its callers ill-formed without changing the overload resolution, so we can be sure we’re seeing the complete set of all the call-sites of this specific overload. (We hope that that set is empty, so that we can safely remove the overload.)


Now, each overload above actually corresponds to two signatures, and it might be that one of those two signatures is unused while the other isn’t. (See “Default function arguments are the devil” (2020-04-18).) For example, suppose I hypothesize that overload #3’s default argument is never used. I can’t test that by simply removing the bool parameter’s default value, because that would change the overload resolution for this call-site:

foo(0);

This used to call #3 as the best match; but with #3’s default argument removed, it will silently call #2 instead.

So actually, even before I started =delete’ing, I would refactor the above overload set into one overload per signature (with appropriate inline markings if needed — not shown):

void foo(const char *, int, bool);  // #1a
void foo(const char *s, int i) { return foo(s, i, false); }  // #1b
void foo(const char *, bool);  // #2a
void foo(const char *s) { return foo(s, false); }  // #2b
void foo(int, bool);  // #3a
void foo(int i) { return foo(i, false); }  // #3b

Then I could safely try =delete’ing the one-argument signature #3b while leaving #3a alone.


See also:

Posted 2022-11-11