4

Specifically, I want the following code to fail:

void a(void*){}
int main(){
    a(0); // FAIL
    a(NULL); // FAIL
    a(nullptr); // success
}

And I want the following code to compile:

void a(int){}
void a(void*){}
int main(){
    a(0); // calls first a
    a(NULL); // calls first a; that's why I have -Werror
    a(nullptr); // calls second a
}

The following code does not compile currently, but should according to my rule:

void a(std::size_t){}
void a(void*){}
int main(){
    a(0); // two candidates
}

Any idea how to make g++ behave like that?

Hristo Venev
  • 972
  • 5
  • 17
  • never tried it but maybe you can make 'a' a class with an 'explicit' constructor and you can override the () operator? – Kam Aug 23 '14 at 14:16
  • @Kam operator() also follows normal overload resolution rules. Also that can't be done for member functions. – Hristo Venev Aug 23 '14 at 14:17
  • Are you ok with spelling it `zero` instead of `0`? That would change a bit what is possible without patching the compiler. – Marc Glisse Aug 23 '14 at 14:42
  • @Marc Glisse #define zero (0-0) will probably work. But I want 0. I think I'm done with clang (-fno-zero-is-null, have to test). Then I'll go to GCC. I'll probably submit this as an extensions. – Hristo Venev Aug 23 '14 at 14:44
  • coming to this late in the day but #define zero (int) 0 will also work – Jimmy Jun 03 '15 at 10:35

5 Answers5

5

You can compile with -Wzero-as-null-pointer-constant to get a warning when you use 0 or NULL instead of nullptr. To promote that to an error, I believe using -Werror=zero-as-null-pointer-constant would work.

Unfortunately, this is simply a warning and is not able to change overload resolution rules. I also believe NULL must be defined as 0 rather than nullptr in order for the warning to catch it, but at least as of GCC 4.9, std::is_null_pointer<decltype(NULL)>::value is false and GCC warns when using NULL.

chris
  • 60,560
  • 13
  • 143
  • 205
1

Given that NULL is either identical to 0 or nullptr, I don't think you can force a C++ compiler to behave the way you describe it. I could imagine using clang's AST interface to detect the cases exactly the way you describe. I'd expect that typical C++ code will contain a number of intentional uses of 0 and/or NULL to mean pointers and/or integers as appropriate.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • I'll add a third example that currently fails to compile but will succeed after the changes I've described. I'm currently trying to do that in clang. – Hristo Venev Aug 23 '14 at 14:19
1

This may not be perfect, but if you trully want to have overloads with int and pointer, you could use some helper class like this:

#include <iostream>
#include <iomanip>
using std::cout;
using std::endl;

template<typename T = void> class ptr {
    T* it;
public:
    ptr(T* it = nullptr): it(it) {}
    ptr(const ptr<T>&) = default;
    ptr& operator = (const ptr<T>&) = default;
    operator T* () { return it; }
    T& operator * () { return *it; }
    T* operator -> () { return it; }
    ptr& operator += (int x) { it += x; return *this; }
    ptr& operator -= (int x) { it -= x; return *this; }
    ptr& operator ++ () { ++it; return *this; }
//  etc...
public:
    template<typename P>
      ptr(P* it): it(it) {}
    template<typename P>
      ptr(ptr<P> it): it((T*)it) {}
};
template<> class ptr<void> {
    void* it;
public:
    ptr(void* it = nullptr): it(it) {}
    ptr(const ptr<void>&) = default;
    ptr& operator = (const ptr<void>&) = default;
    operator void* () { return it; }
public:
    template<typename P>
      ptr(P* it): it(it) {}
    template<typename P>
      ptr(ptr<P> it): it((void*)it) {}
};

void a(std::size_t x) {
    cout << "first: " << x << endl; }
void a(ptr<const int> p) {
    cout << "second: " << (p ? *p : -1) << endl; }
void a(ptr<int> p, ptr<> q) {
    cout << "third: " << (p ? *p : -1) << ", "
        << (q ? "some" : "null") << endl;
    a(p); }
int main(){
    a(0);           // first: 0
    a(NULL);        // first: 0 but warning [-Wconversion-null]
    a(new int(3), nullptr); // third: 3, null + second: 3
}

It is not finished (maybe remove that explicit, add more operators, special conversion from nullptr_t, etc), just and idea.

EDIT: Few changes in code, template constructors and conversion to ptr<const int> test.

firda
  • 3,268
  • 17
  • 30
  • Before I had reference-counted intrusive pointers but I found out I didn't need them (reference count guaranteed by caller). I wanted it to run faster so I removed them and now the code fails to compile. – Hristo Venev Aug 23 '14 at 14:46
  • This helper is no overhead. Maybe you should just remove the reference-counting and stay with original code. – firda Aug 23 '14 at 14:47
  • P.S.: You can mark all the methods __attribute__((always_inline)) to be sure there is no overhead. – firda Aug 23 '14 at 14:49
  • Reference counting is sometimes necessary so I'll have to support strong pointers, weak pointers and simply pointers. This will be fun! – Hristo Venev Aug 23 '14 at 14:52
  • Why not using std::shared_ptr and std::weak_ptr + something like my ptr? (conversion operator and more needed) – firda Aug 23 '14 at 14:54
  • Because cpython doesn't use std::shared_ptr – Hristo Venev Aug 23 '14 at 15:10
  • Ahh, ok. May the update help you a bit with that simple pointer ;) Good luck :) – firda Aug 23 '14 at 15:34
0

Here is a relatively simple solution to the first problem (it requires C++11):

struct must_be_void_ptr{
    must_be_void_ptr(void* p) : p(p) {}
    must_be_void_ptr(int) = delete; // Disallow implicit conversion from 0
    void* p;
    operator void*() { return p; }
};

void a(must_be_void_ptr p){
    void* vp = p;
}

int main(){
    a(nullptr);
    a(0);
}
TonyK
  • 16,761
  • 4
  • 37
  • 72
0

Use:

gsl::not_null

From Guideline Support Library. I highly recommend GSL. It's created and backed by many C++ experts, Bjarne Stroustrup himself and Herb Sutter among them. And the C++ Core Guidelines are actively being integrated into the compiler warnings and static analyzers.

bolov
  • 72,283
  • 15
  • 145
  • 224