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;
}
Foi útil?

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 para int64_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 abranger uint64_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ção testLimitsf separada para float, talvez usando long 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 usar testLimitsf 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 um uint8_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 no switch, 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top