lundi 4 février 2019

Dangers of an explicit conversion operator with a 2nd implicit one in the same class

I find myself in a situation where I'm tempted to put two conversion operators in a single class. Normally I would avoid this like the plague. But with C++ 11 and explicit conversion operators, it seems what I want to achieve is both reasonable and safe. However I need a sanity check.

Consider the following class ("Distance") meant to tie together a distance value with its unit of measurement.

enum class Unit { None = 0, Meter = 1, Foot = 2, }; // None means INVALID

class Distance
{
    Unit   m_unit;
    double m_value;

public:
    Distance() : m_unit(Unit::None), m_value(0.f) {}  // construct invalid
    Distance(Unit u, double, v) : m_unit(u), m_value(v) {}

    Unit   unit() const  { return m_unit; }

    // Two functions that I would like to make into cast operators:

    double value() const { return m_value; }
    bool valid() const { return Unit::None != m_unit; }

    // Cast operators I would LIKE to add and use.  Implicitly cast to
    // double but in "if()" scenarios, cast to bool to check validity

              operator double() const { return m_value; }
     explicit operator bool()         { return valid(); }
};

Since a Distance object is meant to be treated as if it were a double value (most of the time), I considered adding the implicit double cast (above). But in certain contexts, a Distance can be considered "invalid", represented by a unit value of Unit::None. In that case I really like the idea of adding the explicit boolean cast.

The end goal is to be able replace code that currently reads like this:

void doSomething(bool b)   {}
void doSomething(double d) {}

void DumpDistance(const Distance& dist)
{
    if (dist.valid())
        std::cout << dist.value() << std::endl;

    doSomething(dist.valid());  // properly calls bool version  
    doSomething(dist.value());  // properly called double version
}

into this:

void DumpDistance(const Distance& dist)
{
    if (dist)                           // treated as bool (good)
        std::cout << dist << std::endl; // treated as double (good)

    doSomething(dist);                  // calls double overload (good)
}

In my testing, it seems the explicit bool operator does the trick. But I'm not sure. It looks like the desired overloads are being called. But I have a built-in aversion to casting operators burned into me in dark old days of the 1990s.

So my question:

Is this really is as safe as it seems? Is there some obvious case I'm missing in which the compiler would complain about an ambiguous conversion or even worse, silently calls one operator when I wanted another?

(To be clear: I'm not asking about the necessity of this nor about its readability. Only about the dangers of compile-time ambiguities or conversions I don't want to happen. I need to solidify the concept of explicit conversion operators in my head. )

Aucun commentaire:

Enregistrer un commentaire