niedziela, 22 listopada 2015

Another C++ nasty feature

I'm fond of C++ weirdness, really. This language is full of traps, and it shocks me once in a while.

Let's look at this piece of code, a part of a larger module:

void validate_date() {

    // ...

    boost::optional<unsigned> clock_hour;
    boost::optional<unsigned> am_pm_clock;
    
    // ... fill these fields

    if (some sanity check failed) {
        
        report_error("user has entered wrong time: %d %s",
            *clock_hour
            *am_pm_clock ? "AM" : "PM");
    }
}

We would expect that in case of an error following line will be reported: "user has entered wrong time: 123 PM". Obvious. But please look closer at the code, do you see any mistake? There is one... dirty... hard to notice. I'll give you a minute.

So, the mistake is lack of comma between expressions *clock_hour and *am_pm_clock. However, the code is valid! It compiles! And it took me a little longer than a minute to understand what happened. Explanation is:
  • *clock_hour evaluates to expression of type unsigned;
  • then compiler sees * - a multiplication operator;
  • so checks if multiplication of unsigned (on the left side) with boost::optional<unsigned> (on the right side) is possible;
  • it is, because boost::optional<T> has conversion operator to type T.
We can rewrite the whole expression, now it should be clear:

    ((*clock_hour) * unsigned(am_pm_clock)) ? "AM" : "PM"

In result method is called with a single parameter of type cont char*.

It's bizarre, it's terrible. A language should help a programmer. In my opinion implicit conversions is the worst feature of C++.