patterncMinor
C function to print arrays of arbitrary data type
Viewed 0 times
arraysarbitraryfunctiontypeprintdata
Problem
C is naturally clumsy at polymorphism but I wanted to avoid duplication so I wrote this function. But I'm sure there are ways left to improve it. Please not that for brevity's sake not all data types have been included.
void print_int(const void *number) {
int result = *((int*) number);
printf("%d ", result);
}
void print_double(const void *number) {
double result = *((double*)number);
printf("%f ", result);
}
/// Prints arrays of arbitrary type
/// \param array The array to print
/// \param length The length of the array
/// \param type The data type xxx of the array elements in the form of "TYPE_XXX"
/// \return 1 in case of an invalid data type
int print_array(const void *array, size_t length, char *type) {
void (*function)(const void *number);
void *cast_array;
size_t size;
if (!strcmp(type, TYPE_INT)) {
function = print_int;
cast_array = (int *) array;
size = sizeof(int);
} else if (!strcmp(type, TYPE_DOUBLE)) {
function = print_double;
cast_array = (double *) array;
size = sizeof(double);
} else {
return 1;
}
for (size_t i = 0; i < length; ++i) {
function(cast_array + i * size);
}
printf("\n");
return 0;
}Solution
First of all this is a good attempt to provide polymorphism for print functionality. Few comments that might help:
I studied the print_array function and we can certainly do few improvements to it.
1) int print_array(const void array, size_t length, char type)
The type parameter is defined as char *. Since this function will support only a predefined types its better to have those as enum values. for e.g. you could do a typedef as
2) if (!strcmp(type, TYPE_INT))
We can avoid these comparisons by making use of a Map like feature. Your current approach requires changing print_array function for any new type that we add. For e.g. tomorrow if you add support for print_char you will have to make those changes in print_array as well. Wouldn't it be better if print_array required no change. Ok so how to do it. In C++ there is a rule to replace conditional statements with polymorphism or maps. We will use maps here.
Since we already changed the type to an enum we can define a new struct which holds what functions to call for what data-types and the size of data-type as below:
Now we create a mapping for data-types to function and store it in a global variable say print_map
Now your print_array function changes just to few lines i.e. a lookup into this print_map for the type that is passed.
Note that I have added few asserts to make sure that the user passes proper values for type and that the implementer has add all data-type mappings in print_map. Now whenever you define a new print type say print_char you don't need to change print_array. Just add that function and its mapping in print_map and everything should work as it is.
Hope this helps.
I studied the print_array function and we can certainly do few improvements to it.
1) int print_array(const void array, size_t length, char type)
The type parameter is defined as char *. Since this function will support only a predefined types its better to have those as enum values. for e.g. you could do a typedef as
typedef enum{
INT = 0,
DOUBLE,
MAX_TYPES,
}SUPPORTED_TYPES;2) if (!strcmp(type, TYPE_INT))
We can avoid these comparisons by making use of a Map like feature. Your current approach requires changing print_array function for any new type that we add. For e.g. tomorrow if you add support for print_char you will have to make those changes in print_array as well. Wouldn't it be better if print_array required no change. Ok so how to do it. In C++ there is a rule to replace conditional statements with polymorphism or maps. We will use maps here.
Since we already changed the type to an enum we can define a new struct which holds what functions to call for what data-types and the size of data-type as below:
typedef struct{
void (*function)(const void *number);
int size_of_elements;
}FunctionMap;Now we create a mapping for data-types to function and store it in a global variable say print_map
const FunctionMap print_map[] = {
[INT] = {print_int,sizeof(int)},
[DOUBLE] = {print_double,sizeof(double)},
};Now your print_array function changes just to few lines i.e. a lookup into this print_map for the type that is passed.
int print_array(const void *array, size_t length, SUPPORTED_TYPES type)
{
assert(type<MAX_SUPPORTED);
assert((sizeof(print_map)/sizeof(FunctionMap)) == MAX_SUPPORTED);
void (*function)(const void *number);
const void *cast_array = array;
size_t size;
size_t i;
function = print_map[type].function;
size = print_map[type].size_of_elements;
for (i = 0; i < length; ++i) {
function(cast_array + i * size);
}
printf("\n");
return 0;
}Note that I have added few asserts to make sure that the user passes proper values for type and that the implementer has add all data-type mappings in print_map. Now whenever you define a new print type say print_char you don't need to change print_array. Just add that function and its mapping in print_map and everything should work as it is.
Hope this helps.
Code Snippets
typedef enum{
INT = 0,
DOUBLE,
MAX_TYPES,
}SUPPORTED_TYPES;typedef struct{
void (*function)(const void *number);
int size_of_elements;
}FunctionMap;const FunctionMap print_map[] = {
[INT] = {print_int,sizeof(int)},
[DOUBLE] = {print_double,sizeof(double)},
};int print_array(const void *array, size_t length, SUPPORTED_TYPES type)
{
assert(type<MAX_SUPPORTED);
assert((sizeof(print_map)/sizeof(FunctionMap)) == MAX_SUPPORTED);
void (*function)(const void *number);
const void *cast_array = array;
size_t size;
size_t i;
function = print_map[type].function;
size = print_map[type].size_of_elements;
for (i = 0; i < length; ++i) {
function(cast_array + i * size);
}
printf("\n");
return 0;
}Context
StackExchange Code Review Q#150220, answer score: 3
Revisions (0)
No revisions yet.