8

I have seen comments or answers on SoF stating that overloading the cast operator to bool is dangerous and I should prefer the void* operator instead. Still I would like to ask if it is dangerous practice to use this operator in my use case and if yes why.

I implement a simply geometry library and one of its most basic classes is a point that I define in the following way:

struct point {
  double x, y;
  bool real;
  point(double x_, double y_): x(x_), y(y_), real(true) {}
  point(): x(0), y(0), real(true) {}

  operator bool() {
    return real;
  }
};

I will try to explain why I need the cast operator to bool. I have another class called line and I have a function declared as follows:

point intersect(const line& a, const line& b);

Now having overloaded the cast operator to bool of a point I can write both: point p = intersect(a,b); and if (intersect(a,b)) thus either getting the intersection point of two lines or checking if two lines intersect. I use this in other places as well but I believe this example is enough to show the usage of the cast operator. Is my usage of the cast operator dangerous and if it is could you please give an example when the effect will not be as expected?

EDIT: one small addition thanks to juanchopanza : in this project I am limited to not using c++11, so I can not make the operator explicit.

Ivaylo Strandjev
  • 69,226
  • 18
  • 123
  • 176
  • If you have C++11, you can remove danger by making the cast operator `explicit`. – juanchopanza Dec 05 '13 at 08:43
  • @juanchopanza I know but I am limited to pre-`c++11` – Ivaylo Strandjev Dec 05 '13 at 08:44
  • 1
    If you cannot be bothered to implement your own `safe_bool`, see this answer for a set available in boost: http://stackoverflow.com/questions/11781584/safe-bool-idiom-in-boost – Nim Dec 05 '13 at 09:08
  • 1
    The others answered why the cast to bool is a bad idea. I'll also say: The design is pretty flawed. Your struct is 4 byte larger than required, it contains a non-canonical member that is only required for one operation. Instead: Return an optional from intersect, or implement `bool intersects()` predicate. – Sebastian Dec 05 '13 at 10:23
  • There is no **cast** operator. This is a **conversion** operator. – Pete Becker Dec 05 '13 at 13:05
  • 1
    Concerns about inadvertent conversions tend to be greatly overblown. What danger are you concerned about? That someone might write `int x = intersect(a,b)` and think that it actually does something useful? – Pete Becker Dec 05 '13 at 13:08

6 Answers6

5

That is not good enough. Since you're stuck with C++03, safe-bool idiom is what you should be using if you need such a thing at all (otherwise, explicit conversion function should be preferred).

An alternative solution is to return boost:optional instead:

boost::optional<point> intersect(const line& a, const line& b);

I would adopt this approach (even in C++11), as it looks semantically better — parallel lines don't have intersection point, so the return value should indeed be optional (or maybe value).

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • I think that thanks to the link in your answer I understand why overloading bool is dangerous. But will `void*` be better in that sense? – Ivaylo Strandjev Dec 05 '13 at 08:58
  • @IvayloStrandjev: Please go through the link and understand why it is called *safe* bool idiom. – Nawaz Dec 05 '13 at 09:00
  • I can't seem to understand why implementing safe bool idiom as proposed makes an attempt to write `p1 < p2` where both objects are of the same type. I verified compilation fails, but I don't understand **why** – Ivaylo Strandjev Dec 05 '13 at 09:31
  • 2
    @IvayloStrandjev: because function pointers (or what's relevant here: pointers-to-member-function) aren't `<`-comparable. – Steve Jessop Dec 05 '13 at 09:32
  • @SteveJessop ahhh that's what I missed. I did not know that. I was wondering why do we use a function pointer not a simple pointer. Thank you. – Ivaylo Strandjev Dec 05 '13 at 09:33
  • Isn't it true that even if I adopt this way of implementing a safe bool, if I write `!(a == b)` it will compile and evaluate to true? http://ideone.com/pNNczF Is there a way to fix that behavior? – Ivaylo Strandjev Dec 05 '13 at 09:47
  • @Nawaz you need to perform a few more fixes. Note the signature of the comparison operators - you should remove the first parameters. – Ivaylo Strandjev Dec 05 '13 at 10:09
3

Yes, this is dangerous. Just for example, let's consider your point exactly as-is, with no definition of operator+. Despite that lack, if we try to add two point objects, the compiler won't object at all:

point a, b;

std::cout << a + b;

This works by converting each point to bool, then adding the bools. In an integer context, false converts to 0 and true converts to 1, so we can expect the code above to print out 2. Of course, I've just used addition as an example. You could just as well do subtraction, multiplication, division, bitwise operations, logical operations, etc. All of them will compile and execute, but obviously produce worthless results. Likewise, if we pass a point to some function that only accepts a numeric type, the compiler won't stop s or complain -- it'll just convert to bool, and the function will get either 0 or 1 depending on whether real was true or not.

The safe-bool idiom is safe (well, less dangerous anyway) because you can test a void * in a Boolean context, but a void * won't implicitly convert to much of anything else like bool will. You (mostly1) can't accidentally do arithmetic, bitwise operations, etc., on them.


1. There are a few holes anyway, mostly involving calling something else that does some sort of explicit conversion on the void *. Being able to mark conversion operators explicit is better, but honestly they give a lot more improvement in readability than safety.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • You're assuming safe-bool idiom uses `void*`? – Nawaz Dec 05 '13 at 09:17
  • @Nawaz: There are a number of different things that have been called by that name over the years, some using conversion to `void *`, some overloading comparison to `bool`, a few doing even stranger things. Conversion to `void *` is probably the most common though. – Jerry Coffin Dec 05 '13 at 09:20
  • 1
    The link in Nawaz's answer gives an example why not to use `void*`. `a < b` would be valid with possible undefined behavior. I was also surprised that you called this the "safe bool idiom". – Steve Jessop Dec 05 '13 at 09:20
  • @SteveJessop: Yes, as I said: `explicit` really is better. In all honesty, however, actual instances of misbehavior from a conversion to `void *` seem to be pretty rare in practice, even though it's easy to come up with a half dozen (or so) ways it *could* lead to a problem. – Jerry Coffin Dec 05 '13 at 09:25
  • 1
    @JerryCoffin: well, examples are non-existent in well-tested code, same as any other error ;-) Static type checks are not *necessary*, but since C++ has them you try not to trample all over them. The weird thing is just that what you call "safe bool idiom" but then say has some holes, is exactly what Nawaz and I think was the motivation for inventing what we call "safe bool idiom". All previous efforts including `void*` being "unsafe" to varying degrees. – Steve Jessop Dec 05 '13 at 09:37
2

Strange that those people didn't tell you why overloading operator bool() is dangerous.

The reason is that C++ has implicit conversion from bool to all other numeric types. So if you overload operator bool() then you lose a lot of type checks that users of the class would normally expect. They can supply a point anywhere that an int or float is expected.

Overloading operator void*() is less dangerous because there's less that void* converts to, but still has the same fundamental issue that the void* type is used for other things.

The idea of the safe bool idiom is to return a pointer type that doesn't convert to anything used in any API.

Note that there would be no need to worry about this if you were willing to write if (intersect(a,b).real), or perhaps if (intersect(a,b).exists()). C++03 does have explicit conversions. Any function with one parameter (or non-static member function with no parameters) is a conversion of sorts. C++03 just doesn't have explicit conversion operators.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 2
    I still don't like the idea of `real` or `exists()`. `optional` or `maybe` would be better. – Nawaz Dec 05 '13 at 09:58
  • @Nawaz: sure, in this case it's strange to define a type that represents something that may or may not actually be a point. But just generally, if you're going to have an object that might be in an error state, I think people are over-committed to the idea that you should be able to test that state by bunging the object in boolean context. You're allowed to decide that's just too much hassle to implement :-) If you're up for extending the Euclidean plane, then you could make it `intersect(a,b).isfinite()`. – Steve Jessop Dec 05 '13 at 13:51
1

You might run into all kind of trouble due to implicit conversions from bool to a numeric value. Also, your line class got an (in my point of vew) dangerious member 'bool real', to carry results of function calls. An intersection function returning an intersection value or NaN if the lines do not intersect could resolve your problem.

Example

Having a (infinite) line class holding an origin p and a vector v:

struct Line {
    Point p;
    Vector v;

    Point operator () (double r) const {
        return p + r * v;
    }
};

And a function calculating an intersection value

/// A factor suitable to be passed to line a as argument to calculate the
/// inersection point.
/// - A value in the range [0, 1] indicates a point between
///   a.p and a.p + a.v.
/// - The result is NaN if the lines do not intersect. 
double intersection(const Line& a, const Line& b) {
    double d = a.v.x * b.v.y - a.v.y * b.v.x;
    if( ! d) return std::numeric_limits<double>::quiet_NaN();
    else {
        double n = (b.p.x - a.p.x) * b.v.y
                 - (b.p.y - a.p.y) * b.v.x;
        return n/d;
    }
}

Then you can do:

double d = intersection(a, b);
if(d == d) { // std::isnan is c++11
    Point p = a(d);
}

Or

if(0 <= d && d <= 1) {
    Point p = a(d);
}
0

For example, early in the C++ 2003 Standard class std::basic_ios had the following conversion operator

operator void*() const

Now as the new C++ Standard has keyword explicit this operator was substituted for

explicit operator bool() const;

So if you will add keyword explicit for your operator I think it will not be dangerous.:)

The other way is to define operator bool operator !() instead of the conversion operator.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
0

Although there are already a few very good answers I would like to assemble a bit more complete answer with explanation of why the proposed solution does work.

1.Why is my solution wrong? The compiler performs implicit conversions from bool to numeric types resulting in unexpected operators being defined. For instance:

point p;
if (p < 1)

Will compile while I did not expect it to. Changing the operator for conversion to operator void*() will improve the things but only slightly as there some operators(although less than for bool) that are defined for void * and their existence will result again in unexpected behavior.

2.So what is the correct solution. The correct solution can be found here. But I will include the code for my class so that I can explain why it works and why do we solve the problem in this way:

class point {
  typedef void (point::*bool_type)() const;
  void not_supported() const; // NOTE: only declaration NO body!

public:
  double x, y;
  bool real;
  point(double x_, double y_): x(x_), y(y_), real(true) {}
  point(): x(0), y(0), real(true) {}

  operator bool_type() {
    return (real == true)? &point::not_supported : 0;
  }

  template<typename T>
  bool operator==(const T& rhs) const {
    not_supported();
    return false;
  }

  template<typename T>
  bool operator!=(const T& rhs) const {
    not_supported();
    return false;
  }
};

Now first I will explain why do we have to use a type that is a function pointer and not a plain pointer. The answer is stated by Steve Jessop in a comment below Nawaz's answer. function pointers are not < comparable, thus any attempt to write p < 5 or any other comparison between a point and some other type will fail to compile.

Why do we need to have an method with no body (not_supported)? Because in this way each time we try to instantiate the templated methods for comparison(that is == and !=), we will have a function call of a method with no body that will cause a compile error.

Ivaylo Strandjev
  • 69,226
  • 18
  • 123
  • 176
  • Your `point` class should support `==` operator because a point class should be comparable with other points. And the implementation of `operator==` in the wiki page, has nothing to do with safe-bool idiom per se. It is just to demonstrate that safe-bool idiom doesn't convert into `bool` or something which can be compared. – Nawaz Dec 05 '13 at 13:47
  • If I do not overload == I was able to compare my class to another class with similar structure with no compilation error. I tried this. – Ivaylo Strandjev Dec 05 '13 at 15:34
  • I didn't say that. I said your class should support `==`. Here is what I meant : http://coliru.stacked-crooked.com/a/fb22e2a0baaed5f2 – Nawaz Dec 05 '13 at 16:02
  • In fact I don't **have to** overload this operator. And what is more important even if I do I will never implement it the way you do, as strict comparison of doubles is not a good idea – Ivaylo Strandjev Dec 05 '13 at 19:27