Estou lidando com uma API razoavelmente uniforme fornecida por um fornecedor e gostaria de verificar -- e lidar -- com quaisquer falhas de forma unificada também. Para esse fim, escrevi o seguinte 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);
O comando acima compila bem com clang, mas o g++13 e o Microsoft VC++ reclamam dos dois argumentos NULL:
... error: invalid conversion from 'int' to 'const char*' [-fpermissive]
87 | auto code = func(args...
Substituir os dois NULL
s por nullptr
resolve o problema, mas por que isso importa?
Provavelmente, o NULL
é transformado em 0x0
ou mesmo 0
em algum lugar pelo pré-processador, mas a chamada original nunca levantou uma "sobrancelha". Usar NULL era perfeitamente adequado em:
handlepath(handle, path, NULL, 0, coll, NULL);
Por que isso é um problema (para alguns compiladores) quando usado em um wrapper?
ATUALIZAÇÃO : /usr/include/sys/_null.h
no meu sistema FreeBSD tem o seguinte código:
#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
Então:
para C,
NULL
é(void *)0
;para clang++
NULL
enullptr
são a mesma coisa, enquanto para GNU pode não ser...
Em muitas implementações,
NULL
é apenas um#define
para um literal inteiro0
, por exemplo:Você pode atribuir diretamente um literal
0
a qualquer ponteiro, e é por isso que passarNULL
diretamente para a função de destino funciona bem.Como você está passando
NULL
um parâmetro de modelo, esse parâmetro está sendo deduzido como tipoint
, como diz a mensagem de erro.Nesta chamada:
awrap()
resolverá algo como isto:Para atribuir uma
int
variável a um ponteiro, você precisa de uma conversão de tipo explícita, que você não está usando.Enquanto
nullptr
is do tiponullptr_t
, que também pode ser atribuído diretamente a qualquer ponteiro. Há apenas 1 valor possível denullptr_t
. Então, ao passarnullptr
para um parâmetro de template, esse parâmetro será deduzido como tiponullptr_t
. E o compilador sabe como atribuir anullptr_t
a um ponteiro.Nesta chamada:
awrap()
resolverá algo como isto:Então, ao chamar seu modelo, você deve usar
nullptr
.Mas, se você quiser usá-lo
NULL
, você deve fazer a conversão de tipo como(char*)NULL
(ou equivalente), ou qualquer outro tipo de ponteiro que o parâmetro esteja esperando, por exemplo:NULL
é uma macro que pode ser definida comonullptr
ou como um literal inteiro de valor zero (por exemplo,0
ou0L
). Qual deles é definido pela implementação, mas como apenas a última opção é válida em C e pré-C++11, as chances de ver a última são boas.nullptr
é um objeto denullptr_t
. Qualquer expressão desse tipo é sempre uma constante de ponteiro nulo , o que significa que pode ser convertida implicitamente em um valor de ponteiro nulo de qualquer tipo de ponteiro.Uma expressão inteira de valor zero, no entanto, nem sempre é uma constante de ponteiro nulo. Especificamente, apenas literais inteiros de valor nulo são constantes de ponteiro nulo que podem ser convertidas em tipos de ponteiro.
Então, se você passar
0
diretamente para uma função esperando umchar*
que funcione bem, o literal0
será uma constante de ponteiro nulo e poderá ser convertido para o tipo de ponteiro implicitamente, resultando em um valor de ponteiro nulo.Mas você está passando o literal para
awrap
como um tipo inteiro (por dedução). Quando você usa o argumento em ,func(args...)
ele ainda é uma expressão inteira com valor zero, mas não é um literal inteiro . Portanto, o argumento não é uma constante de ponteiro nulo e não pode ser convertido implicitamente parachar*
.Então, você não pode passar
0
um parâmetro de ponteiro pelo wrapper porque você deve nomear o literal exatamente onde a conversão deve ser aplicada. E se você pode usarNULL
é definido pela implementação. Funciona seNULL
acontecer de ser definido comonullptr
, mas não se for um literal inteiro de valor zero.Esse é um bom exemplo do porquê only
nullptr
deve ser usado. O comportamento de conversão implícito de literais inteiros de valor zero existe apenas por razões históricas herdadas de C. Se a linguagem fosse projetada hoje, duvido que alguém adicionaria um caso especial tão estranho às conversões. Normalmente, as conversões possíveis são determinadas pelo tipo e categoria de valor das expressões. Esse é o único caso especial em que o valor e a construção gramatical afetam o comportamento de conversão.