Why does overload resolution select pointer type for 0 but not 1, when it could select ellipses in either case?

Viewed 99

I have the following snippet of code:

void eval(void*) { std::cout << "hello world\n"; }
void eval(...) { }
int main(int argc, char *argv[])
{
    std::cout << "0: "; eval(0);
    std::cout << "1: "; eval(1);
    return 0;
}

which gives the output:

0: hello world
1: 

My question is: Why does overload resolution select the void* version of eval instead of the ... version for 0, but not for 1? It seems like in both cases it could infer that the argument is an int and take the variadic version.

3 Answers

Because of backwards compatibility, 0 is convertible to a pointer. It was used as the NULL pointer. Nowadays nullptr is used, but 0 still needs to be convertible, otherwise old code would not compile anymore.

If you compile with the highest warning level enabled in your compiler, chances are the compiler will warn you whenever 0 is used as a pointer.

The int octal literal 0 is implicitly convertible to the void* pointer type, but 1 is not. This is a pre-C++11 thing (nullptr and its type nullptr_t were introduced in C++11) and is here to stay else existing code would break.

Overload resolution favours a non-variadic function over a variadic one.

Putting these two things together explains your program output.

Why does overload resolution select the void* version of eval instead of the ... version for 0

Because 0 is a null pointer constant.

but not for 1?

Because 1 is not a null pointer constant.

The standard rule:

[conv.ptr]

A null pointer constant is an integer literal ([lex.icon]) with value zero or a prvalue of type std​::​nullptr_­t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type ...


It seems like in both cases it could infer that the argument is an int and take the variadic version.

The C-style variadic argument is the least preferred choice by overload resolution, even when it doesn't need conversion of the argument type that another candidate would require. As long as there is any other valid candidate, the other candidate is chosen. In this case, there is the void* overload which is valid for 0 but not for 1.

If you had an overload eval(int), that would be chosen for both 0 and 1.

P.S. This property of overload resolution of C-style variadic arguments is sometimes used for implementing type traits by dark template magicians.

Related