A very short war story on too much overloading

On my previous post “Inheritance is for sharing an interface (and so is overloading)” (2020-10-09), a couple of Reddit commenters expressed disbelief at my third section, where I claimed that the vector::erase overload set was poorly designed. They had never run into the common bug

// Oops!
v.erase(
    std::remove_if(v.begin(), v.end(), isOdd)
);

For more on erasing from STL containers, see “How to erase from an STL container” (2020-07-08).

Here’s another example of grief caused by overuse-of-overloading, which I just ran into at work last week.

class mgr_t : public asio_actor {
    ~~~
    virtual std::string getname() const = 0;
    virtual result_t subscribe(actor::ptr tgt, msghandler handler,
        priority::level prio, const std::string type,
        bool enrich = false) = 0;
    virtual result_t subscribe(actor::ptr tgt, msghandler handler,
        priority::level prio, selector f, unsigned &id,
        bool enrich = false) = 0;
    virtual result_t subscribe(actor::ptr tgt, msghandler handler,
        priority::level prio,
        bool enrich = false) = 0;
    virtual void subscribe_connect(actor::ptr tgt, connhandler handler,
        priority::level prio) = 0;
    virtual void subscribe_disconnect(actor::ptr tgt, connhandler handler,
        priority::level prio) = 0;
    ~~~
};

And then there’s a caller in some other repository that does this:

this->message_ = std::string("subscription failed");
return g_mgr->subscribe(ctl_actor::instance(), stats,
    priority::level::high, std::string("queue-stats"));

You might look at those lines and think: Ah, the casts to std::string are redundant. This is just a Java programmer’s attempt to keep track of where “string objects” are created. I can remove them. You’d be right about the first cast, but it turns out you’d be wrong about the second!

return g_mgr->subscribe(ctl_actor::instance(), stats,
    priority::level::high, std::string("queue-stats"));

is a call to mgr_t::subscribe(actor::ptr, msghandler, priority::level, const std::string, bool = false). But

return g_mgr->subscribe(ctl_actor::instance(), stats,
    priority::level::high, "queue-stats");

is a call to mgr_t::subscribe(actor::ptr, msghandler, priority::level, bool = false)! Because "queue-stats", after decaying to const char*, would rather implicitly convert to the built-in type bool than to the user-defined type std::string. (Godbolt.)

The above snippet exemplifies many sins which I’ve previously discussed on this blog. See:

Nevertheless, I think the primary sin here — or at least the “first among equals” — is that it has three overloads of subscribe, doing radically different things with (some of) their arguments!

Posted 2020-10-11