Hi everyone,

At the moment I'm trying to create a class that contains a DOMNode* pointer, something like this

class MyDOMNode
{
public:
    MyDOMNode (DOMNode* node)
     : mDomNode (node)
    {
    }

    bool operator== (const MyDOMNode& rhs)
    {
        if (mDomNode == rhs.mDomNode)
            return true;
        return false;
    }
    bool operator!= (const MyDOMNode& rhs)
    {
        return !(mDomNode == rhs.mDomNode);
    }

private:
    DOMNode* mDomNode;
};

What I'm trying to do in the main program is this

MyDomNode node = ...
while (node != 0)
{
    do stuff
}

However when I try to compile I'm getting an error "no match for operator !=" in "node != 0"... would I need to create additional != and == functions to accept an int value and check this against the pointer in the class? Or am I doing this completely wrong :S

Any help is much appreciated,

Stephen

GCC-g++ 4.1.2, pretty old but haven't had a chance to compile it on another version - will try it later on hopefully :)

This works for me and I'm using the same g++ (gcc version 4.1.2 20080704).

#include <iostream>
using namespace std;
typedef int DOMNode;
class MyDOMNode {
  public:
    MyDOMNode (DOMNode* node): mDomNode (node) {
       cout << "Constructor: " << node << endl;
    }
    bool operator== (const MyDOMNode& rhs) {
        if (mDomNode == rhs.mDomNode)
            return true;
        return false;
    }
    bool operator!= (const MyDOMNode& rhs) {
        return !(mDomNode == rhs.mDomNode);
    }
  private:
    DOMNode* mDomNode;
};

int main(){
   DOMNode t(33);
   MyDOMNode node(&t);
   if (node != 0 ) 
      cout << "Not equal" << endl;
   return 0;
}

Output

$ ./a.out
Constructor: 0xbfc9205c <-- Output from MyDOMNode node(&t);
Constructor: 0          <-- Output from call to node.operator!=(0)
Not equal                   which causes MyDOMNode (DOMNode* node)
$                           to run with 0 as the node.

See how the node != 0 causes the constructor to run with 0 as the input parameter.

The compilation success is contigent on the fact that the compiler allows 0 to be implicitly converted to the pointer type DOMNode*. I'm not sure what is the standard behavior required here. In general, if you replace 0 with NULL, it should work, because NULL must be convertible to any pointer type, and for compilers that don't automatically convert 0 to pointer types, they define NULL as ((void*)0) which solves that problem.

In any case, you probably don't really want to allow implicit conversions of numbers into MyDOMNode objects. And since the only time you really want to compare a MyDOMNode object to a number is when you do either (node == 0) or (node != 0), in any other case, you would compare two nodes. A common trick to solve this problem is to create an implicit conversion to a bool as so:

class MyDOMNode {
  public:
    // ..

    operator bool() const {
      return mDomNode;
    };

  private:
    DOMNode* mDomNode;
};

Then, you can test your nodes with these expressions:

(node == 0)   becomes   (!node)
(node != 0)   becomes   (node)

That's a lot more convenient, isn't it?

The compilation success is contigent on the fact that the compiler allows 0 to be implicitly converted to the pointer type DOMNode*. I'm not sure what is the standard behavior required here.

The integer 0 is always recognized as a null pointer when in pointer context. I'd be very very surprised if any modern compiler didn't allow that conversion. However, the OP could be using something ancient. The code looks fine even for older compilers, but I don't have any of them handy to test with, so I can't say for certain.

In general, if you replace 0 with NULL, it should work, because NULL must be convertible to any pointer type, and for compilers that don't automatically convert 0 to pointer types, they define NULL as ((void*)0) which solves that problem.

C++ doesn't include a cast for NULL because that would produce annoying warnings about conversion from a pointer to void. For example:

int *p = ((void*)0); // Bzzt! Cannot convert void* to int* without a cast

To the best of my knowledge, NULL has been defined as the following since the early days of the ISO standard:

#define NULL 0

So in general, NULL and 0 are synonymous, where NULL would be used to make it clear you intended a pointer context. Ideally, with the advent of C++11, nullptr would be used instead, and the whole issue becomes moot. :D

A common trick to solve this problem is to create an implicit conversion to a bool as so:

Bear with me for getting into the nitty gritty. I tend to shy away from implicit conversions like that because they've proven problematic for me in the past with unintended consequences. That's one of the reasons why standard classes such as std::string have an explicit member function data() and c_str() instead of implicit conversions to char*.

Have you found that an implicit conversion to bool is generally safe from unexpected conversions?

The integer 0 is always recognized as a null pointer when in pointer context. I'd be very very surprised if any modern compiler didn't allow that conversion. However, the OP could be using something ancient. The code looks fine even for older compilers, but I don't have any of them handy to test with, so I can't say for certain.
..
So in general, NULL and 0 are synonymous, where NULL would be used to make it clear you intended a pointer context. Ideally, with the advent of C++11, nullptr would be used instead, and the whole issue becomes moot. :D

Points taken. However, the definition of NULL and integral-pointer conversions have been the subject of variations between compilers, even with fairly recent ones. I can't pin-point exactly which compilers are the problem, and no longer have easy access to them, but I definitely remember having problems related to that with BCB and some older MSVC compilers. And certainly, today, one would be better off using nullptr in any case.

Have you found that an implicit conversion to bool is generally safe from unexpected conversions?

I do share your concerns about implicit conversions, and that is why I mostly try to remember to make my single-argument constructors explicit. In general, the more dangerous implicit conversions are those that are between somewhat similar types (like different representations of the same thing, like std::string and C-style strings), and I have had problems related to that in the past. I found that the pitfalls of implicit conversions mostly occur when writing a DSEL with lots of operator overloading and different algebraic types. When I wrote my linear algebra and geometry libraries, I had to be really careful with this. At the end, I found it was just safer to have only explicit constructors and named-functions for conversions.

However, conversion to bool is not only common, but quite safe too. For example, iostream and standard smart-pointer classes have them. It is very convenient to have an implicit conversion to bool, as in while( cin >> value ) and things like that. And, it is safe because the standard guarantees that the bool type is the lowest-ranking integral type, meaning that it will be the conversion that will be considered last by the compiler, and once converted to bool, it is unlikely to be implicitly converted up to a higher type. In some sense, you could say that conversion to bool is the exception that makes the rule.

But still, implicit conversions (operator or constructor) aren't completely forbidden either, they can be useful sometimes, as long as you can avoid the pitfalls and remember that robustness and correctness is more important than syntactic-sugar.

It is very convenient to have an implicit conversion to bool, as in while( cin >> value ) and things like that.

With C++11 that particular conversion was made explicit bool:

explicit operator bool() const;

But before that, due to unintended consequences, the effect was achieved through an implicit conversion to void*:

operator void*() const;

At the very least there's a precedent for avoiding it. IIRC, one of the reasons was conversion to other integral types in typos and nonsense, like if (cout < 123). Granted, those are obscure, but std::ios_base is a very heavily used class, so it may just have been covering for unknowns.

This article has been dead for over six months. Start a new discussion instead.