1

I know that NULL is #defined to be 0. It seems to be an int constant that gets converted to a pointer type. So what happens when there are two overloaded functions: one takes a pointer type, another takes an int type. How does this work vs. nullptr?

#include <iostream>
using namespace std;

void callMe(int* p)
{
    cout << "Function 1 called" << endl;
}

void callMe(int i)
{
    cout << "Function 2 called" << endl;
}

int main()
{
    callMe(nullptr);
    callMe(NULL);

    return 0;
}

I know that callMe(nullptr) will definitely call the first function. But which function will be called by callMe(NULL)? I compiled this code on my computer. There was only one warning.

g++ -std=c++11 nullptr_type.cpp
nullptr_type.cpp: In function 'int main()':
nullptr_type.cpp:44:14: warning: passing NULL to non-pointer argument 1 of 'void callMe(int)' [-Wconversion-null]
   callMe(NULL);
          ^

My code compiled without any problems, and when I run it I see that callMe(NULL) called Function 2. Visual Studio also compiles the code and it calls Function 2.

However, when I try to compile my code on the online GeeksForGeeks IDE, I get some compiler errors.

https://ide.geeksforgeeks.org/UsLgXRgpAe

I want to know why these errors occur.

prog.cpp: In function 'int main()':
prog.cpp:17:13: error: call of overloaded 'callMe(NULL)' is ambiguous
  callMe(NULL);
             ^
prog.cpp:4:6: note: candidate: void callMe(int*)
 void callMe(int* p)
      ^
prog.cpp:9:6: note: candidate: void callMe(int)
 void callMe(int i)
      ^

The same errors are generated by the online Ideone IDE and online TutorialsPoint CodingGround IDE. Is this actually defined behavior or not?

What about this scenario? Which functions will get called here?

#include <iostream>
using namespace std;

void callMe(int* p)
{
    cout << "Function 1 called" << endl;
}

void callMe(char* cp)
{
    cout << "Function 2 called" << endl;
}

void callMe(nullptr_t np)
{
    cout << "Function 3 called" << endl;
}

void callMe(int i)
{
    cout << "Function 4 called" << endl;
}

int main()
{
    callMe(nullptr);
    callMe(NULL);

    return 0;
}

A more general question, but I think that this is where it is all going to: How do you know which function calls are ambiguous or not when overloading functions? I want to know the answer to this question in the context where there are multiple functions which are competitors for the argument. If there would have been only one function, it would take the argument without error. But what happens when several functions seem to be equally likely to take that argument?

The linked question is about using NULL in C. My question is about C++. The C programming language does not have operator overloading, and hence most of the issues that I touched upon are only C++ specific things.

===========================================================================

This is a related question which explains the issue and the solution to it in detail. There's a lot of useful answers in that one.

What are the advantages of using nullptr?

Galaxy
  • 2,363
  • 2
  • 25
  • 59
  • There's many questions here: "What does passing `NULL` call different overloads with different compilers according to the standard?", "How do you know which function calls are ambiguous or not when overloading functions?", and "what happens when several functions seem to be equally likely to take that argument?". – Mooing Duck Jul 18 '18 at 00:05
  • Possible duplicate of [Why NULL is not predefined by the compiler](https://stackoverflow.com/questions/16124397/why-null-is-not-predefined-by-the-compiler) – Mooing Duck Jul 18 '18 at 00:06
  • 1
    @Galaxy: "*How do you know which function calls are ambiguous or not when overloading functions?*" You know it by either A) reading the totality of the function overloading part of the C++ specification, or B) learn some general rules and actively avoid cases that might be complicated. For an example of the latter: never use `NULL` again. This is literally *why* `nullptr` was invented. – Nicol Bolas Jul 18 '18 at 00:25
  • For what it's worth, clang gives the error, `call to 'callMe' is ambiguous`. Raising an error strikes me as sensible behavior. – John Perry Jul 18 '18 at 00:26
  • For an example of the latter: never use NULL again. https://memegenerator.net/img/instances/71101421/yes-sir.jpg – Galaxy Jul 18 '18 at 00:31
  • @Galaxy Now _that's_ a weird link, why ever did you post it? – Paul Sanders Jul 18 '18 at 00:59

2 Answers2

6

I know that NULL is #defined to be 0.

This is not true in general, it could be defined to nullptr. It could be 0L. There are other possibilities too, e.g. a version of gcc I tried defines it to __null which is obviously a null pointer constant extension of gcc.

How do you know which function calls are ambiguous or not when overloading functions?

By reading the Overload Resolution section of the C Standard. Which is one of the most complicated parts of it. In brief, different conversion sequences are ranked and some conversion sequences have a higher rank than others. There's an introduction on cppreference.

  • callMe(0) resolves to (int) because Exact Match is preferred over Conversion. (integer to pointer conversion is required to call the (int *) version)
  • callMe(nullptr) resolves to (int *) because there is not even any conversion available from nullptr to int
  • callMe(NULL) depends on how NULL is defined. If 0 or nullptr, see above. If 0L then it's ambiguous because a Conversion is required to match either case and integer conversion has the same rank as integer-to-pointer conversion. Etc.
M.M
  • 138,810
  • 21
  • 208
  • 365
2

I know that NULL is #defined to be 0

This is not something that's guaranteed by the standard. NULL may be any valid null pointer literal, which includes 0, 0L and nullptr.

However, if we assumed that it was the case for your standard library implementation that NULL was in fact defined to be 0, then overload resolution would behave exactly as it would behave when you pass a literal 0.

It seems to be an int constant that gets converted to a pointer type.

0 is in fact both an int and a pointer constant. However, if there is a conflict, int is preferred by overload resolution (and also template argument detection).

But which function will be called by callMe(NULL)?

Is implementation defined, depending on how NULL was defined.

How do you know which function calls are ambiguous or not when overloading functions?

The pre-condition for knowing whether a function call will be ambiguous is to know the type of the expression that you use as an argument. Since the type of the NULL expression is implementation defined, there are situations where implementations resolve overloads differently - or fail to resolve due to ambiguity.

In the situation where you know the type of the expression, the standard rules will tell you exactly when the call is ambiguous, and when one overload is preferred over another. Overload rules are quite complicated though and there are quite a few of them. The section [over] of the standard should be most relevant.

[over.match.viable]: From the set of candidate functions constructed for a given context ([over.match.funcs]), a set of viable functions is chosen, from which the best function will be selected by comparing argument conversion sequences and associated constraints ([temp.constr.decl]) for the best fit ([over.match.best]). ...

[over.best.ics] ... If a function that uses the ambiguous conversion sequence is selected as the best viable function, the call will be ill-formed because the conversion of one of the arguments in the call is ambiguous.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326