HiveBrain v1.2.0
Get Started
← Back to all entries
patterncMinor

C function to print arrays of arbitrary data type

Submitted by: @import:stackexchange-codereview··
0
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

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.