dangee1705
dangee1705

Reputation: 3520

adding two structs representing different types together

I have a struct like so:

typedef enum any_type{
    ANY_TYPE_CHAR,
    ANY_TYPE_UCHAR,
    ANY_TYPE_SHORT,
    ANY_TYPE_USHORT,
    ANY_TYPE_INT,
    ANY_TYPE_UINT,
    ANY_TYPE_LONG,
    ANY_TYPE_ULONG,
    ANY_TYPE_FLOAT,
    ANY_TYPE_DOUBLE,
} any_type;

typedef struct any{
    any_type type;
    union{
        char as_char;
        unsigned char as_uchar;
        short as_short;
        unsigned short as_ushort;
        int as_int;
        unsigned int as_uint;
        long as_long;
        unsigned long as_ulong;
        float as_float;
        double as_double;
    };
} any;

which represents a variable which is one of the types specified. There are four possible operations on these structs, which are addition, subtraction, multiplication and division.

My question is, is there an efficient way to do these operations on them without doing lots of if statements for every case? That would result in 4 * 10 * 10 = 400 if/else if statements for these four operations alone, which is certainly not efficient!

My code is in C

Thanks in advance.

Upvotes: 4

Views: 915

Answers (4)

chux
chux

Reputation: 154315

To avoid a combinational nightmare, consider that there are 3 groups of types, unsigned integers, signed integers, floating point.

A goal is to determine which of the 3 groups the operation is to use. Then get the value of the operand per its type and the target group. Do the math per that group. Then save per the group and the highest ranking type.


For each of the 10 types, create 10 functions to fetch the data and return as the widest unsigned integer. Another 10 for signed integer and lastly 10 for floating point.

For each for the 10 types, do the same to create functions to set the data. So far 60 small functions.

To access the correct function, use a table look up in a function. 3 more functions for getting, 3 more for setting.

An example addition function is at the end. It looks for the highest ranking type and gets/adds/sets based on the one of 3 groups.

Now add 3 more functions for -,/,*. Total about 60 + 6 + 4 functions.

Bonus: To add a new function like %, only takes 1 more function to write.

#include<assert.h>

typedef enum any_type{
    // Insure these are in rank order
    // ANY_TYPE_CHAR left out for now
    ANY_TYPE_SCHAR,
    ANY_TYPE_UCHAR,
    ANY_TYPE_SHORT,
    ANY_TYPE_USHORT,
    ANY_TYPE_INT,
    ANY_TYPE_UINT,
    ANY_TYPE_LONG,
    ANY_TYPE_ULONG,
    ANY_TYPE_FLOAT,
    ANY_TYPE_DOUBLE,
    ANY_TYPE_N,
} any_type;

typedef struct any{
    any_type type;
    union{
        signed char as_schar;
        unsigned char as_uchar;
        short as_short;
        unsigned short as_ushort;
        int as_int;
        unsigned int as_uint;
        long as_long;
        unsigned long as_ulong;
        float as_float;
        double as_double;
    };
} any;

/////////////////////////////////////
unsigned long any_uget_schar(const any *x) {
  return (unsigned long) x->as_schar;
}
unsigned long any_uget_uchar(const any *x) {
  return x->as_uchar;
}
/* 8 more */

unsigned long any_get_unsigned(const any *x) {
  static unsigned long (*uget[ANY_TYPE_N])(const any *x) = {
    any_uget_schar, any_uget_uchar, /* 8 others */ };
  assert(x->type <  ANY_TYPE_N);
  return (uget[x->type])(x);
}

/////////////////////////////////////
signed long any_sget_schar(const any *x) {
  return x->as_schar;
}
signed long any_sget_uchar(const any *x) {
  return x->as_uchar;
}
/* 8 more */

signed long any_get_signed(const any *x) {
  static signed long (*sget[ANY_TYPE_N])(const any *x) = {
      any_sget_schar, any_sget_uchar, /* 8 others */ };
  assert(x->type <  ANY_TYPE_N);
  return sget[x->type](x);
}
/////////////////////////////////////

double any_get_fp(const any *x); // similar for floating point

/////////////////////////////////////

void any_uset_schar(any *x, unsigned long y) {
  x->as_schar = (signed char) y;
}

void any_uset_uchar(any *x, unsigned long y) {
  x->as_uchar = (unsigned char) y;
}

/* 8 more */

void any_set_unsigned(any *x, unsigned long y) {
  static void (*uset[ANY_TYPE_N])(any *x, unsigned long y) = {
    any_uset_schar, any_uset_uchar, /* 8 others */ };
  assert(x->type <  ANY_TYPE_N);
  uset[x->type](x,y);
}

/* 10 more for any_sset_... */

/* 10 more for any_fset_... */

///////////////////////////////////////////////

static const char classify[] = "susususuff";


any any_get_add(any a, any b) {
  any sum;
  sum.type = max(a.type, b.type);
  assert(sum.type <  ANY_TYPE_N);
  switch (classify[sum.type]) {
    case 's':
      any_set_signed(&sum, any_get_signed(&a) + any_get_signed(&b));
      break;
    case 'u':
      any_set_unsigned(&sum, any_get_unsigned(&a) + any_get_unsigned(&b));
      break;
    case 'f':
      any_set_fp(&sum, any_get_signed(&a) + any_get_signed(&b));
      break;
    default:
      assert(0);
  }
  return sum;
}

Upvotes: 3

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726809

This is going to require a lot of code, because combinatorial explosion is part of the problem.

Use a table-driven approach: define two tables, one for converting between types, and one for performing operations on them.

For conversion make a function pointer type

typedef any (*convert_any)(any val, any_type target);

and make a table of conversions keyed on any_type:

convert_any conversion[] = { convert_char, convert_uchar, convert_short, ... };

with implementations like this for each type (you'll need ten of these):

static any convert_char(any val, any_type target) {
    any res = { .type = target };
    switch (target) {
        case ANY_TYPE_CHAR: res.as_char = val.as_char; break;
        case ANY_TYPE_INT: res.as_int = (int)val.as_char; break;
        ...
    }
    return res;
}

For operations make another function pointer type:

typedef any (*operation_any)(any left, any right);

You will need to make a 3D array of such pointers - one for each triple of operation, the type of its left operand, and the type of its right operand.

Implementations would look like this:

static any add_int(any left, any right) {
    any lhs = conversion[left.type](left, ANY_TYPE_INT);
    any rhs = conversion[right.type](right, ANY_TYPE_INT);
    any res {.type = ANY_TYPE_INT, .as_int = lhs.as_int + rhs.as_int};
    return res;
}
static any add_double(any left, any right) {
    any lhs = conversion[left.type](left, ANY_TYPE_DOUBLE);
    any rhs = conversion[right.type](right, ANY_TYPE_DOUBLE);
    any res {.type = ANY_TYPE_DOUBLE, .as_double = lhs.as_double + rhs.as_double };
    return res;
}

There will be forty such implementations - one for each result type and operation pair. The 3D table will contain 400 entries, depending on the type of the result the operation needs to produce. The call will look up into 3D array, find operation_any pointer, pass two arguments, and get a result like this:

any res = operations[PLUS_OP][left.type][right.type](left, right);

Upvotes: 3

Loamsiada
Loamsiada

Reputation: 454

Sorry, but you have to do case switching since the compiler has to generate different CPU instructions for different types. The kind of operation is defined by the choice of the variable.

Upvotes: 0

mattn
mattn

Reputation: 7723

You should make expr structure. This mean one node of AST (Abstruct Syntax Tree). And it have op. This mean +,-,*,/ for the lhs, rhs. do_expr caluculate expression. Finally, dump_any print the value.

#include <stdio.h>
#include <assert.h>

typedef enum any_type{
  ANY_TYPE_CHAR,
  ANY_TYPE_UCHAR,
  ANY_TYPE_SHORT,
  ANY_TYPE_USHORT,
  ANY_TYPE_INT,
  ANY_TYPE_UINT,
  ANY_TYPE_LONG,
  ANY_TYPE_ULONG,
  ANY_TYPE_FLOAT,
  ANY_TYPE_DOUBLE,
} any_type;

typedef struct any{
  any_type type;
  union{
    char as_char;
    unsigned char as_uchar;
    short as_short;
    unsigned short as_ushort;
    int as_int;
    unsigned int as_uint;
    long as_long;
    unsigned long as_ulong;
    float as_float;
    double as_double;
  };
} any;

typedef enum op_type{
  OP_PLUS,
  OP_MINUS,
  OP_MULT,
  OP_DIVID,
} op_type;

typedef struct {
  int op;
  any lhs;
  any rhs;
} expr;

int
do_expr(expr* e, any *r) {
  switch (e->op) {
    case OP_PLUS:
      r->type = e->lhs.type;
      r->as_int = e->lhs.as_int + e->rhs.as_int;
      return 0;
      break;
    default:
      fprintf(stderr, "unknown operation\n");
      break;
  }
  return 1;
}

void
dump_any(any* a) {
  switch (a->type) {
    case ANY_TYPE_INT:
      printf("%d\n", a->as_int);
      break;
    default:
      assert(!"unknown type");
      break;
  }
}

int
main(int argc, char* argv[]) {
  expr e1 = {
    .op = OP_PLUS,
    .lhs = { .type = ANY_TYPE_INT, as_int: 1, },
    .rhs = { .type = ANY_TYPE_INT, as_int: 2, },
  };

  any ret;
  if (do_expr(&e1, &ret) == 0) {
    dump_any(&ret);
  }

  return 0;
}

Upvotes: 0

Related Questions