Question

Comment rendre la fonction ci-dessous générique pour uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t et float_t?

Je n'aime pas répéter la même logique dans tous les cas que vous pouvez le voir. La seule différence dans chaque cas est le casting.

Je voudrais idéalement une solution qui adhère à la norme C et est donc portable. Toutes les idées sont les bienvenues.

Merci.

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;
}
Était-ce utile?

La solution

Bien toi pourrait Extraire les moulages:

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, si les différents types forment une plage contigu de valeurs de 0, vous pouvez même faire deux tableaux de pointeurs de fonction et faire:

bool isWithinLimits = testLimits(&limits, pData, loadptrs[key->type], converts[key->type]);

Remarques:

  • Vous devez toujours écrire deux fonctions pour chaque type, bien qu'elles soient facilement macro-générées si vous préférez.
  • Cela ne semble pas vraiment la peine pour ce petit code.
  • j'ai choisi int64_t Puisqu'il est capable de représenter toutes les valeurs de tous les types entiers que vous utilisez, donc les conversions int64_t Ne jetez jamais d'informations et ne modifiez jamais le résultat d'une comparaison par rapport à la même comparaison dans le type de source. Mais si tu voulais aussi couvrir uint64_t, alors vous ne pouvez pas utiliser le même type pour tout, car il n'y a pas de type entier qui peut représenter toutes les valeurs de tous les types entiers. Vous auriez également besoin d'un séparé testLimitsf fonctionner pour float, peut-être en utilisant long double comme type commun pour la flexibilité future.
  • Edit: je viens de réaliser, en supposant IEEE-754, double En fait, peut représenter exactement toutes les valeurs de tous les types que vous utilisez. Donc, avec une légère restriction de portabilité, vous pouvez utiliser testLimitsf pour tout et affaire en double
  • Êtes-vous sûr que cela vaut la peine de se convertir (par exemple) uint8_t Avant la comparaison? Soit la valeur est dans la plage pour un uint8_t, dans ce cas, vous n'avez pas besoin de vous convertir, vous pouvez simplement faire la comparaison. Ou bien la valeur n'est pas dans la plage, auquel cas la réduction du modulo rend la comparaison un peu dénuée de sens, sauf dans les cas spéciaux de 0 et -1. Donc, cela peut en valoir la peine, si quelque chose que vous n'avez pas déclaré le fait, mais cela me semble louche.
  • Vous avez dit dans un commentaire: "J'essaie de rendre cette fonction plus efficace". Cela pourrait aller à l'encontre de cela. Il est logiquement possible de se connecter testLimits et aussi les appels aux fonctions de coulée dans le switch, mais je ne compterais pas dessus.

Autres conseils

Il n'y a pas de moyen facile de faire une programmation générique comme celle-ci dans C. Si vous êtes inquiet de l'entretien, cela peut être une occasion rare où une macro est appropriée.

Pourquoi ne pas utiliser une macro?

#define DO_MY_WORK(TYPE)\
  if((*(TYPE*)pData > (TYPE)tLimits.zMax) || (*(TYPE*)pData < (TYPE)tLimits.zMin))\
  {\
      isWithinLimits = FALSE;\
  }

La prochaine révision de la norme (C1X) ajoutera la prise en charge des expressions génériques de type (exemple de Wikipedia):

#define cbrt(X) _Generic((X), long double: cbrtl, \
                              default: cbrt, \
                              float: cbrtf)(X)

GCC a un support C1X préliminaire. Je pense que _generic n'est pas encore soutenu, mais gardez cela à l'esprit pour l'avenir.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top