Pergunta trocadilho tipo C
-
28-10-2019 - |
Pergunta
Como posso tornar a função abaixo genérica para uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t e float_t?
Não gosto de repetir a mesma lógica em todos os casos, como você pode ver.A única diferença em cada caso é o elenco.
Eu gostaria idealmente de uma solução que obedeça ao padrão C e, portanto, seja portátil.Todas as ideias são bem-vindas.
Obrigado.
static bool_t IsWithinLimits(const dbKey_t *key, const void *data)
{
bool_t isWithinLimits = TRUE;
limits_t limits = getDefinedLimits(key);
switch(key->type)
{
case TYPE_UINT8:
if((*(const UINT8*)data > (UINT8)limits.max) || (*(const UINT8*)data < (UINT8)limits.min))
{
isWithinLimits = FALSE;
}
break;
case TYPE_UINT16:
if((*(UINT16*)pData > (UINT16)limits.max) || (*(UINT16*)data < (UINT16)limits.min))
{
isWithinLimits = FALSE;
}
break;
case TYPE_UINT32:
...
break;
case TYPE_INT8:
...
break;
case TYPE_INT16:
...
break;
case TYPE_INT32:
...
break;
case TYPE_FLOAT:
...
break;
}
return isWithinLimits;
}
Solução
Bem, você poderia extrair os elencos:
int64_t loadptr_uint8(const void *p) {
return *(uint8_t*)p;
}
int64_t convert_uint8(int64_t val) {
return (uint8_t)val;
}
int testLimits(const limits_t *plimits, const void *pData, int64_t(*loadptr)(void*), int64_t (*convert)(int64_t)) {
return loadptr(pData) <= convert(limits->max) && loadptr(pData) >= convert(limits->min);
}
switch(key->type) {
case TYPE_UINT8:
isWithinLimits = testLimits(&limits, pData, loadptr_uint8, convert_uint8);
break;
// etc
}
Ou, se os vários tipos formarem um intervalo contíguo de valores a partir de 0, você pode até fazer duas matrizes de ponteiros de função e fazer:
bool isWithinLimits = testLimits(&limits, pData, loadptrs[key->type], converts[key->type]);
Observações:
- Você ainda precisa escrever duas funções para cada tipo, embora sejam facilmente geradas por macro, se preferir.
- Realmente não parece valer a pena por este pequeno código.
- Escolhi
int64_t
, pois é capaz de representar todos os valores de todos os tipos inteiros que você usa, então as conversões paraint64_t
nunca descartam informações e nunca alteram o resultado de uma comparação em relação a fazer a mesma comparação em o tipo de fonte. Mas se você também quiser abrangeruint64_t
, você não pode usar o mesmo tipo para tudo, uma vez que não existe um tipo inteiro que pode representar todos os valores de todos os tipos inteiros. Você também precisaria de uma funçãotestLimitsf
separada parafloat
, talvez usandolong double
como o tipo comum para flexibilidade futura. - [Editar: acabei de perceber, assumindo o IEEE-754,
double
na verdade pode representar exatamente todos os valores de todos os tipos que você usa. Então, com uma pequena restrição de portabilidade, você poderia usartestLimitsf
para tudo e lidar em duplas] - Tem certeza de que vale a pena converter para (por exemplo)
uint8_t
antes da comparação? Ou o valor está no intervalo de umuint8_t
, caso em que você não precisa converter, basta fazer a comparação. Ou então o valor não está no intervalo, caso em que a redução do módulo torna a comparação um pouco sem sentido, exceto nos casos especiais de 0 e -1. Portanto, pode valer a pena, se algo que você não declarou justificar, mas parece suspeito para mim. - Você disse em um comentário: "Estou tentando tornar essa função mais eficiente". Isso pode ir contra isso. É logicamente possível inserir
testLimits
e também as chamadas para as funções de conversão noswitch
, mas eu não contaria com isso.
Outras dicas
Não existe uma maneira fácil de fazer uma programação genérica como essa em C. Se você está preocupado com a manutenção, pode ser uma rara ocasião em que uma macro é apropriada.
Por que não usar uma macro?
#define DO_MY_WORK(TYPE)\
if((*(TYPE*)pData > (TYPE)tLimits.zMax) || (*(TYPE*)pData < (TYPE)tLimits.zMin))\
{\
isWithinLimits = FALSE;\
}
A próxima revisão do padrão (C1x) adicionará suporte para expressões genéricas de tipo (exemplo da wikipedia):
#define cbrt(X) _Generic((X), long double: cbrtl, \
default: cbrt, \
float: cbrtf)(X)
gcc tem algum suporte C1x preliminar.Acho que _Generic ainda não é compatível, mas lembre-se disso no futuro.