5

I am trying to figure out the best way to prevent integer 0 from being implicitly cast to nullptr_t and then passed to constructors that take pointers. Explicit doesn't do it, but I can get nullptr_t to cause an ambiguous overload error:

#include <typeinfo.h>

struct A {
    explicit A(char*) { }
};

struct B {
    B(nullptr_t a) = delete;
    B(char*) { }
};

int main(int argc, char* argv[])
{
    A a(0);         // darnit I compiled...
    B b1(0);        // good, fails, but with only b/c ambiguous
    B b2((char*)0); // good, succeeds
    B b3(1);        // good, fails, with correct error
}

Is there a better way than this? Also, what exactly is delete accomplishing here?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
johnnycrash
  • 5,184
  • 5
  • 34
  • 58

2 Answers2

2

Deleting A(int) will not prevent A(char *) being called with nullptr. So you will need to delete A(nullptr_t) as well.

And even this will not protect you from the eventuality that there is some rogue class in the neighbourhood that is implicitly constructible from 0 or nullptr and implicitly converts same to char *.

#include <iostream>

struct A {
    A(int) = delete;
    A(std::nullptr_t) = delete;
    explicit A(char * p) {
        std::cout << "Constructed with p = " << (void *)p << std::endl;
    }
};

struct X
{
    X(long i) : _i(i) {}
    operator char *() const { return (char *)_i; }
    long _i;
};

int main()
{
    X x(0);
    A a(x);
    return 0;
}

The program prints:

Constructed with p = 0

You may reckon that possibility remote enough to be ignored; or you might prefer to delete all constructors, in one fell swoop, whose argument is not precisely of a type that you sanction. E.g. assuming the sanctioned types are just char *:

struct A {
    template<typename T>
    A(T) = delete;
    explicit A(char * p) {}
};

struct X
{
    X(long i) : _i(i) {}
    operator char *() const { return (char *)_i; }
    long _i;
};

int main()
{
    // A a0(0);
    //A a1(nullptr);
    X x(0);
    // A a2(x);
    char *p = x;
    A a3(p); // OK     
    return 0;
}

Here all of the commented out constructor calls fail to compile.

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
  • It doesn't seem like blocking an explicit call with `nullptr` is desired; it seems like more of an unpleasant side effect. – user2357112 Oct 08 '15 at 01:48
  • Does deleting with a template work for you? template A(T) = delete; I have read that templated copy constructors don't disqualify default constructors. I have experienced it too. So deleting them has no effect. – johnnycrash Oct 09 '15 at 16:37
  • I would like char* to be called with nullptr, just not some enum that happened to be 0, which is what was happening to me! I was not clear on this in the question. – johnnycrash Oct 09 '15 at 16:49
2

If you want to stop your constructor from taking 0, one option would be to delete B(int):

B(int) = delete;

That's an unambiguously better match for B(0) than the constructor that takes a char *.

Note that up until C++11, NULL was specified as being of integer type, and even in C++11 and later, it's still likely implemented as #define NULL 0. B(NULL) won't work if you do this. B(nullptr) will work, but still, I'd be wary of whether this is worth doing.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • I think this works for my problem. The compiler looks at a 0 and says it could also be a void*. It then looks at the constructors. If it finds a int constructor it prefers that, sees the delete and stops. – johnnycrash Oct 09 '15 at 16:48