Generic 1 Macro/Function for any value type
While there is an accepted answer, here is my generic implementation of the "problem." It allows you to use a single macro/function, for virtually any value type.
If you do not want to follow along, the source files can be downloaded from this repository.
Just note that there are minor differences, this post is already very long as it is, and I did not want to make it any longer by including the other macros in the repo.
Repo Example
If you use the code from this post instead of the repo, you must also pass the size of the array. Example at the bottom
#include "generic_array.h"
//Initialize Arrays
uint16_t shortarray [10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
float floatarray[3] = {1.0f, 2.5f, -5.25f};
void main()
{
//Length not passed, full array checked
bool containsShort = array_contains(5, shortarray);
or
bool containsFloat = array_contains(2.5f, floatarray);
or
//Length passed, half of array checked
bool containsShort = array_contains(4, shortarray, (5 * sizeof(uint16_t));
}
Generic Union
To start off we need to declare a union, which will allow us to pass any value type to our macro. The main reason we need this is so we can pass floating point numbers without them being cast to an integer, losing their true bits.
This union will allow us to use all (almost) value types up to 64 bits. So unfortunately "long double" will not be able to be used.
/*
* Union which contains all value types.
* Useful for converting from int types to and from floating types, without casting the bits.
* Also makes _Generic macros which call functions to all value types possible.
*
* *Not all values are included. They need to be added.
*/
typedef union generic_union
{
const char* ccp;
char*cp;
char c;
signed char sc;
unsigned char usc;
int i;
unsigned int ui;
short s;
unsigned short us;
long l;
unsigned long ul;
long long ll;
unsigned long long ull;
float f;
double d;
bool b;
enum e;
uint8_t u8;
int8_t s8;
uint16_t u16;
int16_t s16;
uint32_t u32;
int32_t s32;
uint64_t u64;
int64_t s64;
}generic_union_t;
Generic Function
Then we need the function which can take any value(Except for structs).
/*
* Searches an array for a value, and confirms its existence.
*
* /param value The value to find in the array. Can be any basic value type.
* /param array Pointer to the start of the array.
* /param arr_size The size of the array (in bytes).
* /param elem_size The size of the value's type (in bytes).
*
* /returns If the array contains the value.
*/
bool array_contains_generic(const generic_union_t value, const char* array, size_t arr_size, uint8_t elem_size)
{
char* vp = &value; //Pointer to the value
for(int i = 0; i < arr_size; i += elem_size)
{
uint8_t b = 0;
for(b; b < elem_size; b++)
{
if(array[i + b] != vp [b])
{
//Byte not matching, move to next element.
break;
}
}
if(b == elem_size)
{
return true;
}
}
return false;
}
You could just use that function on its own, but we aren't able to use structs. Another problem is we will have to perform a (generic_union_t) cast each time, like the example below.
int array = {1, 2, 3, 4};
bool contains = array_contains_generic((generic_union_t)3, array, sizeof(array), sizeof(int))
So instead you can create a function that takes a pointer, but then you have to declare the value first, which is a little annoying.
int value = 3;
bool contains = array_contains_memory(&value, array, sizeof(array), sizeof(int))
Ability to use structs
So instead of having 2 functions that mostly do the same thing, we will change the first function(array_contains_generic). It will take our value from the stack, place it in a variable, pass it as a pointer to our second function(array_contains_memory).
/*
* Searches an array for a matching segment of memory.
*
* /param value Any value or struct that is in memory.
* /param array Pointer to the start of the array.
* /param arr_size The size of the array (in bytes).
* /param elem_size The size of the value's type (in bytes).
*
* /returns If the array contains the value.
*/
bool array_contains_memory(const uint8_t* value, const uint8_t* array, size_t arr_size, uint8_t elem_size )
{
for(int i = 0; i < arr_size; i += elem_size)
{
uint8_t b = 0;
for(b; b < elem_size; b++)
{
if(array[i + b] != value[b])
{
//Byte not matching, move to next element.
break;
}
}
if(b == elem_size)
{
return true;
}
}
return false;
}
/*
* Searches an array for a value, and confirms its existence.
*
* /param value The value to find in the array. Can be any basic value type.
* /param array Pointer to the start of the array.
* /param arr_size The size of the array (in bytes).
* /param elem_size The size of the value's type (in bytes).
*
* /returns If the array contains the value.
*/
bool array_contains_generic(const generic_union_t value, const char* array, size_t arr_size, uint8_t elem_size)
{
uint64_t v = value.u64; //Store value so we can get pointer
return array_contains_memory(&v, array, arr_size, elem_size);
}
But this still hasn't fixed our casting problem, and it would also be nice to just be able to call "array_contains" instead of "array_contains_..."
Macro (Adds the ability to use one "function prototype" for all use cases)
So here is when the macro comes into play,
This allows us to call "array_contains" with literally any value type, including custom structs of any size. Another benefit is we don't have to pass "elem_size" anymore, as the macro extracts the size by using sizeof(array[0]).
/*
* Macro used to "overload" array_indexOf functions.
* Can be used with arrays or pointers.
*
* /param value The value to search for.
* /param array Pointer to the start of the array.
* /param length The size of the array (in bytes).
*
* /returns If the array contains the value.
*/
#define array_contains(value, array, length) _Generic((value), \
const char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length, sizeof(array[0])), \
char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length, sizeof(array[0])), \
const unsigned char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length,sizeof(array[0])), \
unsigned char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length, sizeof(array[0])), \
const signed char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length, sizeof(array[0])), \
signed char*: array_contains_memory((generic_union_t){.u64 = (uint32_t)value}.cp, array, length, sizeof(array[0])), \
float : internal_call_array_contains_special((generic_union_t)value, array, length, sizeof(float)), \
double : internal_call_array_contains_special((generic_union_t)value, array, length, sizeof(double)), \
default: array_contains_generic((generic_union_t)value, array, length, sizeof(array[0])))
This macro uses _Generic, which will call different functions depending on the type of "value." As you can see, there is still casting involved, but it is all done behind the scenes.
2 Parameter Function/Macro
And then finally, the OP wanted a function with only 2 parameters. That is achieved with another macro. Just note that it will not work with pointer arrays, they must be declared as an actual array.
/*
* "array_contains" without the length parameter.
*/
#define array_contains_2(value, array) array_contains(value, array, sizeof(array))
The repo I posted at the top handles things a bit differently. Instead of having 2 macros for each set of parameters, you are able to call a single macro, like the example under the repo link.
Examples
Here are a few examples of how to use the macro with different data types.
//The struct def for the example
typedef struct example_struct{
int a;
float b;
}example_struct_t;
//The arrays to search
example_struct_t ts_arr[4];
uint16_t shortarray [4] = {1, 2, 3, 4};
int intarray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
float floatarray[10] = {1.0d, 2.5d, -5.25d};
double dubarray[10] = {1.0d, 2.0d, -5.25d, 4.0d, 5.0d, 6.0d, 7.0d, 8.0d, 9.0d, 10.0d};
void main()
{
/* Integer Types*/
bool containsInt = array_contains(3, intarray, sizeof(intarray));
bool containsShort = array_contains(3, shortarray, sizeof(shortarray));
/* Floating Point Types*/
// elem_size not needed, as macro calls sizeof(float/double)
bool containsFloat = array_contains(2.5d, floatarray, sizeof(floatarray));
bool containsDouble = array_contains(-5.25d, dubarray, sizeof(dubarray));
/* Struct*/
ts_arr[0] = (example_struct_t){.a = 5, .b = 2.5f};
ts_arr[1] = (example_struct_t){.a = 5, .b = -2.5f};
ts_arr[2] = (example_struct_t){.a = 3, .b = 2.5f};
ts_arr[3] = (example_struct_t){.a = 4, .b = 1.4f};
example_struct_t ts = (example_struct_t){.a = 3, .b = 2.5f};
//Unfortuantely we still need to cast the struct to a char* (All pointers must be cast)
bool containsStruct = array_contains((char*)&ts, ts_arr, sizeof(ts_arr));
}