Freigeben über


C++ operator overloading trivia

Learned something interesting this week that I'll be working into SafeInt 3. It all started out because if you declare a SafeInt class instance, and then try to use it as an array index, the compiler can't figure out which of the several available integer casts to use for the index. According to the language, an array index should get a ptrdiff_t, which is a 32 or 64-bit signed int, so I'm not sure why the compiler doesn't ask for that, but I digress. I asked an internal alias with a bunch of C++ experts, and immediately got a bunch of questions about why I designed SafeInt to do things the way I did. About the time I was starting to regret asking the question, someone popped up with a question about why I overloaded the Boolean operators (&& and ||), and thought I could replace it with a casting operator to some weird function thing. The initial claim was that this certainly wouldn't cause any side-effects or problems, and given that SafeInt is highly complex, is used thousands of times across Office alone, and it's found more than one compiler bug (not to mention not compiling at all on compilers without really good, current C++ standard template support), I was skeptical to say the least. I was right to be skeptical, as his original suggestion wouldn't even compile at all, but it did lead us to something that's better.

Here's the problem:

#include <stdio.h>

int Bar( int c )

{

    return printf("c = %d\n", c);

}

class PtrThing

{

public:

    PtrThing() : m_pThing(0){}

    bool operator &&( bool b ) const

    {

        return m_pThing && b;

    }

    void* m_pThing;

};

int main(int argc, char* argv[])

{

    PtrThing p;

    if( p && Bar(argc) )

        printf("true\n");

    return 0;

}

Without the overloaded && operator, we can't write a proper if statement using a PtrThing object. We could certainly write another overloaded operator, or a Ptr() method to extract the underlying pointer, and then pass that to an if statement. Given the design goal for SafeInt of being able to replace 'int' with SafeInt<int> and have it not only compile but do the right thing, it seemed reasonable to have an overloaded && and || operator.

However, there's a problem – we're used to things short circuiting. If the first part of a logical AND evaluates to false, we don't evaluate the second, and the converse is true of a logical OR. When you overload the && and || operators, the inputs have to be evaluated, which isn't the way things normally work, leading to programmer astonishment and bugs. This is why it is generally bad to overload these operators, but for SafeInt, it seemed better than not having a logical AND compile at all.

As it turns out, there's a better alternative – overload the cast to bool. Try taking out the operator && above, and add this:

operator bool() const { return !!m_pThing; }

It now not only compiles, but it short-circuits properly. There is a drawback – if I had some situation where a PtrThing could be unexpectedly cast to bool and that wasn't what I wanted, then we could have anything from an ambiguous compile, resulting in errors to an actual runtime bug.

I know this is fairly obscure, and only something that a C++ geek would care about, but I thought it was interesting and could have implications for certain types of classes, especially pointer containers.

Comments

  • Anonymous
    October 02, 2007
    PingBack from http://www.artofbam.com/wordpress/?p=4707

  • Anonymous
    October 02, 2007
    Sorry if you already know this, but I thought i'd comment. Overloading operator bool can be a little bit dodgy since a bool can be promoted to an int, which allows your class to be used in any context where an int type variable is expected as well as allowing arithmetic expressions which might catch you out. Several alternatives are commonly to provide a cast to something which can be used in a boolean context used such as operator void* since that can't be automatically promoted to anything and can't take part in arithmetic, but that has another problem which is that it can still be compared with the ordering operators and can be deleted - which might yield surprising behaviour depending on what you returned. The approach I tend to use is to return a member function pointer as follows typedef void (my_class::* anonymous_bool_type)(); operator anonymous_bool_type() const; and then return a pointer to a private function. I haven't found a compiler this doesn't work on. There is a gotcha that it still allows == and !=, so you have to provide templated overloads for them. A good discussion of this can be found here http://www.artima.com/cppsource/safebool.html [dcl] yes, a bool can be promoted to int, but since SafeInt already has conversions directly to every integral type, it shouldn't be an issue for this application. It could well be an issue for something else. In the context of SafeInt, the approach you mention doesn't compile. I'm not sure why not, but it's likely because it does have other conversions available.

  • Anonymous
    October 02, 2007
    What exactly was the suggestion about the “weird function thing”? The usual idiom when implicit conversion to bool is not desirable is to define a conversion to “unspecified bool”: class PtrThing { public:  PtrThing() : m_ptrThing(0) {}  // The trick requires that the class have  // a member function whose address could  // serve as a “true” value. operator! will  // do just fine.  bool operator!() const { return !m_ptrThing; }  // Now define the unspecified bool type  // as “pointer to member function”  // so that operator! will match it.  // It may even be private so we don’t  // overexpose anything. private:  typedef bool (PtrThing::*unspecified_bool)() const; public:  // Define a conversion to this unspecified bool:  operator unspecified_bool() const  {    return m_ptrThing ? &PtrThing::operator! : 0;  } private:  void m_ptrThing; } With this, we get normal && and || semantic, are able to use PtrThing in expressions expecting a boolean type (since PtrThing is convertible to a pointer to member function and pointers to member functions are boolean types), and, at the same time, do not allow nonsensical operations like (p << 1) and (p + 3). More about the “safe bool” idiom here: http://www.artima.com/cppsource/safebool.html [dcl] yes, that's it - however, p << 1 and p + 3 aren't nonsensical for a SafeInt, and overloads exist for all of those operators. It could be that all the other operators that are there and do make sense for a SafeInt are causing this not to work. Your point is well taken for general classes.