I'm dealing with a fairly uniform vendor-provided API, and would like to check -- and handle -- any failures in a unified fashion too. To that end, I wrote the following wrapper:
template <typename Func, typename... Args>
auto awrap(Func &func, Args&&... args)
{
auto code = func(args...);
if (code >= 0)
return code;
... handle the error ...
};
...
awrap(handlepath, handle, path, NULL, 0, coll, NULL);
The above compiles fine with clang, but g++13 -- and Microsoft VC++ -- both complain about the two NULL-arguments:
... error: invalid conversion from 'int' to 'const char*' [-fpermissive]
87 | auto code = func(args...
Replacing the two NULL
s with nullptr
solves the problem, but why does it matter?
Likely, the NULL
is turned into 0x0
or even 0
somewhere by the preprocessor, but the original call never raised an "eyebrow". Using NULL was perfectly suitable in:
handlepath(handle, path, NULL, 0, coll, NULL);
Why is it a problem (for some compilers), when used in a wrapper?
UPDATE: the /usr/include/sys/_null.h
on my FreeBSD system has the following code:
#ifndef NULL
#if !defined(__cplusplus)
#define NULL ((void *)0)
#else
#if __cplusplus >= 201103L
#define NULL nullptr
#elif defined(__GNUG__) && defined(__GNUC__) && __GNUC__ >= 4
#define NULL __null
#else
#if defined(__LP64__)
#define NULL (0L)
#else
#define NULL 0
#endif /* __LP64__ */
#endif /* __GNUG__ */
#endif /* !__cplusplus */
#endif
So:
for C, NULL
is (void *)0
;
for clang++ NULL
and nullptr
are the same thing, whereas for GNU it may not be...
In many implementations, NULL
is just a #define
for an integer literal 0
, eg:
#define NULL 0
You can directly assign a literal 0
to any pointer, which is why passing NULL
directly to the target function works fine.
Since you are passing NULL
to a template parameter, that parameter is being deduced as type int
, as the error message says.
In this call:
awrap(handlepath, handle, path, NULL, 0, coll, NULL);
awrap()
will resolve to something like this:
auto awrap(decltype(handlepath) &func, decltype(handle) arg1, decltype(path) arg2,
int arg3, int arg4, decltype(coll) arg5, int arg6)
// ^^^^^^^^ ^^^^^^^^
{
auto code = func(arg1, arg2, arg3, arg4, arg5, arg6);
// error: ^^^^ error: ^^^^
...
};
To assign an int
variable to a pointer, you need an explicit type-cast, which you are not using.
Whereas nullptr
is of type nullptr_t
, which can also be assigned directly to any pointer. There is only 1 possible value of nullptr_t
. So, when passing nullptr
to a template parameter, that parameter will be deduced as type nullptr_t
. And the compiler knows how to assign a nullptr_t
to a pointer.
In this call:
awrap(handlepath, handle, path, nullptr, 0, coll, nullptr);
awrap()
will resolve to something like this:
auto awrap(decltype(handlepath) &func, decltype(handle) arg1, decltype(path) arg2,
nullptr_t arg3, int arg4, decltype(coll) arg5, nullptr_t arg6)
// ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
{
auto code = func(arg1, arg2, arg3, arg4, arg5, arg6);
// OK: ^^^^ OK: ^^^^
...
};
So, when calling your template, you should use nullptr
.
But, if you want to use NULL
then you must type-cast it as (char*)NULL
(or equivalent), or whatever other pointer type the parameter is expecting, eg:
awrap(handlepath, handle, path, (char*)NULL, 0, coll, (char*)NULL);