Domanda

Come posso rendere la funzione seguente generica per uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t e float_t?

Non mi piace ripetere la stessa logica in ogni caso come puoi vedere.L'unica differenza in ogni caso è il casting.

Idealmente mi piacerebbe una soluzione che aderisca allo standard C ed è quindi portatile.Tutte le idee sono benvenute.

Grazie.

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;
}
È stato utile?

Soluzione

Bene, potresti estrarre i cast:

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
}

Oppure, se i vari tipi formano un intervallo contiguo di valori da 0, potresti persino creare due array di puntatori a funzione e fare:

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

Note:

  • Devi ancora scrivere due funzioni per ogni tipo, anche se, se preferisci, sono facilmente generate da macro.
  • Non sembra ne valga la pena per questo piccolo codice.
  • Ho scelto int64_t poiché è in grado di rappresentare tutti i valori di tutti i tipi interi che utilizzi, quindi le conversioni in int64_t non eliminano mai le informazioni e non cambiano mai il risultato di un confronto rispetto a fare lo stesso confronto in il tipo di origine. Ma se vuoi anche coprire uint64_t, non puoi usare lo stesso tipo per tutto, poiché non esiste un tipo intero che possa rappresentare tutti i valori di tutti i tipi interi. Avresti anche bisogno di una funzione testLimitsf separata per float, magari utilizzando long double come tipo comune per flessibilità futura.
  • [Modifica: mi sono appena reso conto che, assumendo IEEE-754, double può effettivamente rappresentare esattamente tutti i valori di tutti i tipi che usi. Quindi, con una leggera limitazione alla portabilità, potresti usare testLimitsf per tutto e trattare in doppio]
  • Sei sicuro che valga la pena convertirlo in (ad esempio) uint8_t prima del confronto? O il valore è compreso nell'intervallo per un uint8_t, nel qual caso non è necessario convertire, puoi semplicemente fare il confronto. Oppure il valore non è compreso nell'intervallo, nel qual caso la riduzione del modulo rende il confronto un po 'insignificante tranne nei casi speciali di 0 e -1. Quindi potrebbe valerne la pena, se qualcosa che non hai dichiarato lo rende così, ma a me sembra strano.
  • In un commento hai detto: "Sto cercando di rendere questa funzione più efficiente". Questo potrebbe andare contro quello. È logicamente possibile incorporare testLimits e anche le chiamate alle funzioni di casting nel switch, ma non ci conterei.

Altri suggerimenti

Non esiste un modo semplice per eseguire una programmazione generica come questa in C. Se sei preoccupato per la manutenzione, questa potrebbe essere una rara occasione in cui una macro è appropriata.

Perché non utilizzare una macro?

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

La prossima revisione dello standard (C1x) aggiungerà il supporto per espressioni generiche di tipo (esempio da wikipedia):

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

gcc ha un supporto preliminare per C1x.Penso che _Generic non sia ancora supportato, ma tienilo a mente per il futuro.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top