cstructunion

How to use unions in C for pseudo-polymorphism


Consider a data structure to describe blocks of a matrix in C:

// Matrix block
typedef struct {
    union {
        int    i;
        double d;
    } *a;       // pointer to first element in matrix block
    int  k;     // block index
    int  p;     // no. of block rows
    int  q;     // no. of block columns
    int  m;     // no. of matrix rows
    int  n;     // no. of matrix columns
    int  r;     // MPI rank of block owner
} mtrx_blk;

I would like to use a union for the pointer to the first element of a matrix block. I would like to use the same mtrx_blk structure for different matrix types. In my case matrices can be either of type double or int. For example, if I create two matrix blocks with NBLK = 2; and a matrix A of type double:

// for each block
for (int k = 0; k < NBLK; k++) {

    blk[k] = (mtrx_blk) { .a = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
    }

Matrix A is of size M x N elements and matrix blocks are of size P x Q elements.

The above way to assign to a pointer .a results in an error:

gcc -std=c11 -pedantic-errors struct-union.c -o struct-union
struct-union.c: In function 'main':
struct-union.c:52:36: error: initialization of 'union <anonymous> *' from incompatible pointer type 'double *' [-Wincompatible-pointer-types]
   52 |         blk[k] = (mtrx_blk) { .a = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
      |                                    ^
struct-union.c:52:36: note: (near initialization for '(anonymous).a')

I tried to phrase it as:

// for each block
for (int k = 0; k < NBLK; k++) {

    blk[k] = (mtrx_blk) { .a.d = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
    }

But this also fails to compile with

gcc -std=c11 -pedantic-errors struct-union.c -o struct-union
struct-union.c: In function 'main':
struct-union.c:52:31: error: field name not in record or union initializer
   52 |         blk[k] = (mtrx_blk) { .a.d = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
      |                               ^
struct-union.c:52:31: note: (near initialization for '(anonymous)')
struct-union.c:52:38: error: initialization of 'union <anonymous> *' from incompatible pointer type 'double *' [-Wincompatible-pointer-types]
   52 |         blk[k] = (mtrx_blk) { .a.d = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
      |                                      ^
struct-union.c:52:38: note: (near initialization for '(anonymous).a')

My questions are:

  1. How do I do it correctly?
  2. Is my logic correct and can I use the analogous syntax .a.i = &B[k*P*Q] for a matrix B of an integer data type?

Please find the full source code below:

#include <stdlib.h>

// Matrix block
typedef struct {
    union {
        int    i;
        double d;
    } *a;       // pointer to first element in matrix block
    int  k;     // block index
    int  p;     // no. of block rows
    int  q;     // no. of block columns
    int  m;     // no. of matrix rows
    int  n;     // no. of matrix columns
    int  r;     // MPI rank of block owner
} mtrx_blk;

int main(int argc, char *argv[]) {

    // no. of matrix rows
    int const M = 10;

    // no. of matrix columns
    int const N = 10;

    // no. of blocks
    int const NBLK = 2;

    // no. of block rows
    int const P = M/NBLK;

    // no. of block columns
    int const Q = N;

    // allocate memory for matrix
    double *A = (double*) malloc(M*N*sizeof(double));

    // allocate array of blocks
    mtrx_blk *blk = (mtrx_blk*) malloc(NBLK*sizeof(mtrx_blk));

    // for each block
    for (int k = 0; k < NBLK; k++) {

        blk[k] = (mtrx_blk) { .a.d = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
    }

    // free memory of array of blocks
    free(blk);

    // free memory of matrix
    free(A);

    return 0;
}

Solution

  • I would like to use a union for the pointer to the first element of a matrix block. I would like to use the same mtrx_blk structure for different matrix types. In my case matrices can be either of type double or int.

    In that case, you have not expressed your intent correctly.

    This ...

        union {
            int    i;
            double d;
        } *a
    

    ... declares a as a pointer to a union. It would be matched to an array of such unions,* and probably (though not necessarily) usable with an array of double. But it is unlikely to be appropriate for use with an array of int, given the likelihood that the representation of int on your system is smaller than the representation of double.

    1. How do I do it correctly?

    A fairly low-impact correction would be to replace your pointer to a union with a union of pointers. For example:

    typedef struct {
        union {
            int    *as_int;
            double *as_double;
        } a;        // pointer to first element in matrix block
        int  k;     // block index
        int  p;     // no. of block rows
        int  q;     // no. of block columns
        int  m;     // no. of matrix rows
        int  n;     // no. of matrix columns
        int  r;     // MPI rank of block owner
    } mtrx_blk;
    

    You might then write something like:

        blk[k] = (mtrx_blk) { .a = { .as_double = &A[k*P*Q] }, .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
    

    Alternatively, yes, you can simplify that slightly to

        blk[k] = (mtrx_blk) { .a.as_double = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
    

    That's a valid form, at least. I can't evaluate whether the specifics are correct for what you are trying to achieve.

    There are other possibilities, too, some of which might serve your purposes better. For instance, if you structure it as a union of mostly-congruent, tagged or typedefed structures, then you could have a distinct type for each element type, yet be able to handle them jointly via the union. That could unlock some convenience via type-_Generic macros.

    1. Is my logic correct and can I use the analogous syntax .a.i = &B[k*P*Q] for a matrix B of an integer data type?

    You can initialize the int * member of the union instead of the double * member, yes.


    *subject to some provisos related to the scope of the union type