Joel B
Joel B

Reputation: 13140

Polymorphic function parameters/return values in C

I'm working in an embedded microcontroller and I have to read/write multiple types data to Non-Volatile RAM (NVRAM). I'd like to avoid having to write separate read/write functions for each value to store in NVRAM like this:

void Write_Value1(int value);
int Read_Value1(void);
void Write_Value2(unsigned long value);
unsigned long Read_Value2(void);
...

That is definitely cumbersome to maintain as the list of items to read/write grows. There's a couple of ways to tackle this in C++, templates for example. Using a template I could write a .Read() and .Write(T value) function that each item to read/write would have access to. Is there anyway to get this behavior from C? I suspect that there is some way, using some combination of void pointers and vtables. I'd like to handle this in a more polymorphic fashion and have the function for reading/writing each item to NVRAM called in a similar fashion.

Upvotes: 1

Views: 898

Answers (5)

AnT stands with Russia
AnT stands with Russia

Reputation: 320661

You can [pretty closely] simulate C++ templates in C by using macros.

There are at least two way to do that.


First method: "Rubber stamp" macros

Define declare/define macros for your functions

#define DECLARE_READ_WRITE(suffix, type)\
  type Read_Value_##suffix(void);
  void Write_Value_##suffix(type val);

#define DEFINE_READ_WRITE(suffix, type)\
  type Read_Value_##suffix(void) {\
    /* your code */ \
  }\
  void Write_Value_##suffix(type val) {\
    /* your code */ \
  }

and then in some header file do

DECLARE_READ_WRITE(long, long)
DECLARE_READ_WRITE(int, int)

and in some implementation file

DEFINE_READ_WRITE(long, long)
DEFINE_READ_WRITE(int, int)

which will "generate" declarations and definitions for Read_Value_int, Write_Value_int, Read_value_long and Write_value_long.


Second method: Parameterized include files

Create two header files. One for declarations (read_write.h.template)

TYPE__ CONCAT(Read_Value_, SUFFIX__)(void);
void CONCAT(Write_Value_, SUFFIX__)(TYPE__ val);

and one for definitions (read_write.c.template)

TYPE__ CONCAT(Read_Value_, SUFFIX__)(void)
{
  /* your code */
}

void CONCAT(Write_Value_, SUFFIX__)(TYPE__ val)
{
  /* your code */
}

Here CONCAT is a usual implementation of macro token concatenation (can/should be used in the first method as well).

And then include your "templated" code into an appropriate header file and implementation file as

#define TYPE__ int
#define SUFFIX__ int
#include "read_write.h.template"
#undef TYPE__
#undef SUFFIX__

#define TYPE__ long
#define SUFFIX__ long
#include "read_write.h.template"
#undef TYPE__
#undef SUFFIX__

The same thing in some implementation file with read_write.c.template header.

The latter method has an added benefit of producing debuggable code. I.e. you can step through it in the debugger, just like it typically works with C++ templates.

Upvotes: 9

ArjunShankar
ArjunShankar

Reputation: 23680

You could do it with void pointer and sizes:

void write_value (void *data, size_t size);
void* read_value (size_t size);

Then:

write_value ( (void *) &int_val, sizeof (int));
write_value ( (void *) &ulong_val, sizeof (unsigned long));

intptr = read_value (sizeof (int));
charptr = read_value (sizeof (char));

The read_value then needs to malloc something and return it. This might just be overhead sometimes, when you'd rather read into an automatic. Then do:

/* Declaration.  */
void read_value (void *data, size_t size);

/* Use.  */
read_value (&automatic_var, sizeof (automatic_var));

And finally, you could 'simulate' templates in all sorts of ways.

Upvotes: 4

Man of One Way
Man of One Way

Reputation: 3980

There is no such thing in C. However, you could use a struct approach.

#define T_VOID  0
#define T_INT   1
#define T_ULINT 2

typedef struct 
{
    int type;
    union 
    {            
        int a; 
        unsigned long b; 
    } my_union;
} my_struct;

void write_value(my_struct *s)
{
    switch (s->type) 
    {
        case T_VOID:
        // do something
        break;
        case T_INT: 
        // do something with s->my_union.a
        break;
        case T_ULINT: 
        // do something with s->my_union.b
        break;
    }
}

In the same fashion, read_value would return a my_struct pointer.

Upvotes: 3

David Grayson
David Grayson

Reputation: 87486

Look at the Posix read and write functions and just do something similar:

void read(int address, void * data, int size);
void write(int address, const void * data, int size);

Then later:

read(address, &x, sizeof(x));
write(address, &x, sizeof(x));

Upvotes: 2

chmeee
chmeee

Reputation: 3638

Returning a different type based on the target type is (nearly?) impossible with C. You can mimic it with macros, though. For example:

#define WRITE(x)  write_value(x, sizeof(x))
#define READ(x)  read_value(&x, sizeof(x))

Now you can write write_value() and read_value() to copy data out based on length, taking into account endianness in your internal routine.

Upvotes: 3

Related Questions