EDIT: Oh dear I feel foolish. Of course you're not going to be able to cast from longs to doubles and back - longs have more significant bits. Will casting to a long double improve this?
I'm implementing an Array List in C which I want to be type-agnostic. I also don't want it to be so verbose in usage, thus I'd rather my get functions not return void pointers and require type-casting by the user. Therefore I'm storing an enum for the type (one type for each list), which can be INT, FLOAT, LONG, or DOUBLE. Parameters are passed as doubles (so the compiler will implicitly cast if you pass an int, float or long), then depending on the type of data stored in the list, the double which is passed gets casted to the correct type. Will this always work? Will a number, casted to a double, and then back down to its original type ever have a different value? Are there any other issues with the overarching idea? Barebones implementation is below
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
typedef enum{
INT,
LONG,
FLOAT,
DOUBLE
}TYPE;
typedef struct arrayList{
void * data;
TYPE type;
size_t capacity;
size_t num_elements;
}arrayList;
void reallocateArrayList(arrayList * list, size_t capacity){
TYPE type=list->type;
if(type == INT){
void * toAllocate = malloc(capacity*sizeof(int));
memcpy(toAllocate, list->data, list->num_elements*sizeof(int));
if(list->data!=NULL){
free(list->data);
}
list->data = toAllocate;
}
if(type == FLOAT){
void * toAllocate = malloc(capacity*sizeof(float));
memcpy(toAllocate, list->data, list->num_elements*sizeof(float));
if(list->data!=NULL){
free(list->data);
}
list->data = toAllocate;
}
if(type==LONG){
void * toAllocate = malloc(capacity*sizeof(long));
memcpy(toAllocate, list->data, list->num_elements*sizeof(long));
if(list->data!=NULL){
free(list->data);
}
list->data = toAllocate;
}
if(type==DOUBLE){
void * toAllocate = malloc(capacity*sizeof(double));
memcpy(toAllocate, list->data, list->num_elements*sizeof(double));
if(list->data!=NULL){
free(list->data);
}
list->data = toAllocate;
}
list->capacity=capacity;
if(list->data==NULL) raise(ENOMEM);
}
arrayList * createArrayList(TYPE type){
arrayList * retval = (arrayList *) malloc(sizeof(arrayList));
retval->capacity=256;
retval->data = NULL;
retval->type = type;
retval->num_elements = 0;
reallocateArrayList(retval, 256);
return retval;
}
void add(arrayList * list, double val){
if(list->capacity<=list->num_elements){
reallocateArrayList(list, 4*list->capacity);
}
TYPE type=list->type;
if(type == INT){
((int *)(list->data))[list->num_elements]=(int) val;
}
if(type == FLOAT){
((float *)(list->data))[list->num_elements]=(float) val;
}
if(type==LONG){
((long *)(list->data))[list->num_elements]=(long) val;
}
if(type==DOUBLE){
((double *)(list->data))[list->num_elements]=val;
}
list->num_elements++;
}
double get(arrayList * list, size_t index){
double retval;
if(index>=list->num_elements){
printf("Error: arrayList get index out of range");
raise(SIGSEGV);
}
if(list->type==INT){
retval=((int*)(list->data))[index];
}
if(list->type==FLOAT){
retval=((float*)(list->data))[index];
}
if(list->type==LONG){
retval=((long*)(list->data))[index];
}
if(list->type==DOUBLE){
retval=((double*)(list->data))[index];
}
return retval;
}
No, it is implementation-defined behaviour and it is a very bad idea.
I would use
voids and some helper macros to make it completely universal. It is completely type agnostic and can be used with any data type (also structs, unions and arrays)and some example usage:
https://godbolt.org/z/EYezracjT