Question about customization type C

How to make the function below common for uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t and float_t?

I do not like to repeat the same logic in each case, as you can see. The only difference in each case is casting.

I would ideally like a solution that adheres to the C standard and therefore is portable. Any ideas are welcome.

Thanks.

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; } 
+4
source share
4 answers

Well, you can extract the broadcasts:

 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 } 

Or, if different types form an adjacent range of values ​​from 0, you could even create two arrays of function pointers and do:

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

Notes:

  • You still have to write two functions for each type, although they are easily generated by macros if you prefer.
  • Actually this is not like this little code.
  • I chose int64_t , because it is able to display all the values ​​of all the integer types you use, so conversions to int64_t never discard information and never change the result of the comparison with respect to performing the same comparison in the source type. But if you would also like to cover uint64_t , you cannot use the same type for everything, since there is no integer type that can represent all the values ​​of all integer types. You will also need a separate testLimitsf function for float , possibly using long double as a generic type for future flexibility.
  • [Edit: I just realized, assuming that IEEE-754, double can really accurately represent all the values ​​of all types that you use. Therefore, with a slight portability limit, you can use testLimitsf for everything and deal in double-locale numbers]
  • Are you sure that before matching it is worth converting (for example) uint8_t ? Or the value is in the range for uint8_t , in which case you do not need to convert, you can just do the comparison. Or the value is not in the range, and in this case the modular reduction makes the comparison a little pointless, except in special cases 0 and -1. So it might be worth it if something you didn't say it does it that way, but it seems suspicious to me.
  • You said in a comment: "I'm trying to make this function more efficient." It may go against it. It is logically possible to embed testLimits , as well as calls to cast functions in switch , but I would not count on it.
+1
source

There is no easy way to do this common C programming. If you are worried about maintenance, this can be a rare case when a macro comes up.

+3
source

Why not use a macro?

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

The next version of the standard (C1x) will add support for generic expressions (an example from Wikipedia):

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

gcc has some prior C1x support. I think Generic is not yet supported, but be aware of the future.

+1
source

All Articles