Search code examples
cvoid-pointersdereference

How to Pass (void**) to Function and Efficiently Dereference/Use for Any type?


Woking with a simple function to print a similated 2D matrix, I simply want to be able to pass an array of pointers to type to the function as void**, along with the needed dimensions m x n, the sizeof type, a format string for printf, and a simple flag to distinguish floating-point from integer. The problem I am running into is handling scope for dereferencing the array of pointers so that each element can be properly printed as the original type. Below, the basic scheme is:

void mtrx_prnv (size_t m, size_t n, void **matrix,
                size_t sz, char *fmt, char fp)
{
    if (fp) {    // floating point
        if (sz == 4) {
            float **mtrx = (float **)matrix;
            ...
        }
        else if (sz == 8) {
            double **mtrx = (double **)matrix;
            ...
        }
    }
    else {
        if (sz == 1) {
            char **mtrx = (char **)matrix;
            ...
        }
        else if (sz == 2) {
            ...
        }
        ...
    }
}

The approach works fine, but the problem is that after testing for the floating-point flag 'fp' and sizeof type 'sz', any pointer creation to use for dereferencing is restricted to the scope of the test block and ends up requiring the basic verbatim duplication of the code needed to process the array of pointers in each block. The code ends up being longer than creating a lot of little functions for each type. e.g.:

void mtrx_prn_float (size_t m, size_t n, float **matrix) {}

void mtrx_prn_int (size_t m, size_t n, int **matrix) {}

Is there a better or standard way to pass an array of pointers to type as void**, the sizeof type, and whatever other flags are needed to properly dereference to type without so much duplication of code? If this is the way it has to be, that's fine, but I want to make sure I'm not missing a simple trick. So far my searches haven't produced anything useful (the overwhelming body of information returned by search is about how to get a single type back, not separate all types). How to write C function accepting (one) argument of any type wasn't on point here.

The full function (without the additional logic for int/unsigned separation) is below.

/* printf array of pointers to type as 2D matrix of
   size 'm x n' with sizeof type 'sz', format string
  'fmt' and floating point flag 'fp' (0 - int).
*/
void mtrx_prnv (size_t m, size_t n, void **matrix,
                size_t sz, char *fmt, char fp)
{
    register size_t i, j;

    if (fp) {    /* floating point */

        if (sz == 4) {
            float **mtrx = (float **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else if (sz == 8) {
            double **mtrx = (double **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else
            goto err;
    }
    else {      /* integer (no unsigned yet) */

        if (sz == 1) {
            char **mtrx = (char **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else if (sz == 2) {
            short **mtrx = (short **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else if (sz == 4) {
            int **mtrx = (int **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else if (sz == 8) {
            long **mtrx = (long **)matrix;
            for (i = 0; i < m; i++) {
                char *pad = " [ ";
                for (j = 0; j < n; j++) {
                    printf (fmt, pad, mtrx[i][j]);
                    pad = ", ";
                }
                printf ("%s", " ]\n");
            }
        }
        else
            goto err;
    }

    return;

err:;

    fprintf (stderr, "%s() error: invalid size for fp_flag '%zu'.\n",
                __func__, sz);
}

As suggested in the comments, the declaration/definition is updated to:

void mtrx_prnv (size_t m, size_t n, void *matrix,
                size_t sz, char *fmt, char fp);

Macro Provided Solution

There were several excellent solutions suggested, primarily eliminate the verbatim duplication by creating a macro for the redundant text, and secondly the use of callbacks to print the value of the elements. The macro provided a near drop in solution:

#define PRINT_MATRIX(type) do { \
    type **mtrx = (type **)matrix;  \
    for (i = 0; i < m; i++) { \
        char *pad = " [ "; \
        for (j = 0; j < n; j++) { \
            printf (fmt, pad, mtrx[i][j]); \
            pad = ", "; \
        } \
        printf ("%s", " ]\n"); \
    } \
} while (0)

...

void mtrx_prnvm (size_t m, size_t n, void *matrix,
                size_t sz, char *fmt, char fp)
{
    register size_t i, j;

    if (fp) {    /* floating point */

        if (sz == 4) {
            PRINT_MATRIX(float);
        }
        else if (sz == 8) {
            PRINT_MATRIX(double);
        }
        else
            goto err;
    }
    else {      /* integer (no unsigned yet) */

        if (sz == 1) {
            PRINT_MATRIX(char);
        }
        else if (sz == 2) {
            PRINT_MATRIX(short);
        }
        else if (sz == 4) {
            PRINT_MATRIX(int);
        }
        else if (sz == 8) {
            PRINT_MATRIX(long);
        }
        else
            goto err;
    }

    return;

err:;

    fprintf (stderr, "%s() error: invalid size for fp_flag '%zu'.\n",
                __func__, sz);
}

Solution

  • Considering that you are repeating the bulk of each block verbatim, one obvious solution would be to define and use a macro representing that code (as opposed to a macro for generating per-type functions, as another answer suggests). That would limit physical code duplication, and would not require you to change the structure of your program:

    #define PRINT_MATRIX(type) do { \
        (type) **mtrx = ((type) **)matrix;
        for (i = 0; i < m; i++) { \
            char *pad = " [ "; \
            for (j = 0; j < n; j++) { \
                printf (fmt, pad, mtrx[i][j]); \
                pad = ", "; \
            } \
            printf ("%s", " ]\n"); \
        } \
    } while (0)
    
    void mtrx_prnv (size_t m, size_t n, void *matrix,
                    size_t sz, char *fmt, char fp)
    {
        if (fp) {    // floating point
            if (sz == 4) {
                PRINT_MATRIX(float);
            }
            else if (sz == 8) {
                PRINT_MATRIX(double);
            }
        }
        else {
            if (sz == 1) {
                PRINT_MATRIX(char);
            }
            else if (sz == 2) {
                PRINT_MATRIX(short);
            }
            ...
        }
    }