Generics in C

T

ttl_idiot

This post is about how to get generic modules in a C environment. The
aim is to get close to Ada generics. Using C++ is not an option. It
should all run in a simple C compiler.

When I think of a generic module, it is something that can be used
exactly as it is. The generic H-file and/or C-file, should not have to
be edited AT ALL in order to use it. If it has to be changed in just 1
line, it could possibly still have great reusability properties, but it
is not generic in the sense I mean.

I am going to give a suggestion for a way to do generics, by presenting
5 files as an example. I would very much like comments on how things
could be simplified or extended, but still using only C.

Ok, here we go. Lets say, we want to implement generic powers, by
repeating multiplication. The user of the module have to provide the
type, and a function that performs multiplication on that type.

The generic module is represented by GenPower.h and GenPower.c. The
instantiation module is My.h and My.c, and then we have a test program.
The test program will calculate 3^5 = 3*3*3*3*3 = 243. It is compiled
and run in VC6.

Running the test program prints this to stdout:
Result of power = 243

Let me know what you think of the idea. Good? Bad? Old?
Complicated? (In that case I agree, but I think it is worth it.)


// File GENPOWER.H
// Formal Generic Parameters:
// INSTANCE The name of the instance module.
// ATYPE The instance type.
// (INSTANCE)_Multiply Multiplication function for the type.
// (See prototype below for details.)
//
// void INSTANCE_Multiply(ATYPE *, ATYPE);
// is implemented in your module, otherwise it will be
// missed at link time.
//
// Details of formal parameter functions:
#define MODULE_Multiply INSTANCE()##_Multiply
void MODULE_Multiply(ATYPE *first_operand_and_result, ATYPE
other_operand);
//
// Package: (What this module provides generically)
#define MODULE_Power INSTANCE()##_Power
void MODULE_Power (ATYPE *result, int exponent);
//
// End of file


// File GENPOWER.C
// This is just for #include -ing, Do not link this module.
#include "GenPower.h"
void MODULE_Power (ATYPE *result, int exponent)
{ // Exponent must be at least 1.
ATYPE a;
int i;
a = *result;
for (i=1; i<exponent; i++) {
MODULE_Multiply (result, a);
}
}


// File MY.H
typedef unsigned long MYTYPE;
// Ok, now we have declared our type, lets get the power function
// by instantiating the generic power module:
#define INSTANCE() MY
#define ATYPE MYTYPE
#include "GenPower.H"
//
void MY_Multiply(MYTYPE *, MYTYPE);
// End of file


// File MY.C
#include "my.h"
#include "genpower.c"
// Yes we are including the C file, not the H file.
// That way we actually get an implementation of the function
// void MYTYPE_Power(MYTYPE *, int) right here in this module.
// Of course, we also have to provide the multiplication function:
void MY_Multiply(MYTYPE *first_operand_and_result, MYTYPE
other_operand)
{
// Do something multiplicatish.
(*first_operand_and_result) *= other_operand;
}
// End of file


// Test program
#include <stdio.h>
#include "my.h"
int main(int argc, char* argv[])
{
MYTYPE a;
a = 3;
MY_Power(&a, 5);
printf ("Result of power = %d\n", (int) a);
return 0;
}
// End of file
 
S

santosh

This post is about how to get generic modules in a C environment. The
aim is to get close to Ada generics. Using C++ is not an option. It
should all run in a simple C compiler.

When I think of a generic module, it is something that can be used
exactly as it is. The generic H-file and/or C-file, should not have to
be edited AT ALL in order to use it. If it has to be changed in just 1
line, it could possibly still have great reusability properties, but it
is not generic in the sense I mean.

I am going to give a suggestion for a way to do generics, by presenting
5 files as an example. I would very much like comments on how things
could be simplified or extended, but still using only C.

Ok, here we go. Lets say, we want to implement generic powers, by
repeating multiplication. The user of the module have to provide the
type, and a function that performs multiplication on that type.

In my opinion, that defeats the purpose of generic programming.

<snip>
 
K

Keith Thompson

santosh said:
(e-mail address removed) wrote: [...]
Ok, here we go. Lets say, we want to implement generic powers, by
repeating multiplication. The user of the module have to provide the
type, and a function that performs multiplication on that type.

In my opinion, that defeats the purpose of generic programming.

<snip>

I disagree. It would be nice if the generic module could infer the
appropriate multiplication operator given the type (<OT>as Ada
generics can do</OT>), but that's difficult or impossible to do in C.

Note also that it doesn't just *require* you to provide a
multiplication function, it *allows* you to do so. If you provide a
function that performs addition, the generic will perform
multiplication. It provides a generic way to specify applying some
arbitrary operation repeatedly. In a sense, that's more generic than
just providing a power function.

(I haven't read the code closely enough to verify that this will
actually work.)
 
M

Malcolm McLean

Keith Thompson said:
I disagree. It would be nice if the generic module could infer the
appropriate multiplication operator given the type (<OT>as Ada
generics can do</OT>), but that's difficult or impossible to do in C.

Note also that it doesn't just *require* you to provide a
multiplication function, it *allows* you to do so. If you provide a
function that performs addition, the generic will perform
multiplication. It provides a generic way to specify applying some
arbitrary operation repeatedly. In a sense, that's more generic than
just providing a power function.

(I haven't read the code closely enough to verify that this will
actually work.)
Part of the solution is to have a few types as possible muddying typespace.

However there are some basic functions that most generic operators need.
Namely clone, copy, destroy, serialise, deserialise, print as human-readable
string.

Maths operators are a bit of a red herring. It is nice in theory to
implement an average that works on huge integers and complex types. In
practise, it is easy enough to roll your own, because the need doesn't arise
sufficiently often. In any case, there are problems inherent in the
process - the average of a list of integers is a real, for instance, so what
is the average of a list of huge integers supposed to be?.
 
I

Ian Collins

// File MY.H
typedef unsigned long MYTYPE;
// Ok, now we have declared our type, lets get the power function
// by instantiating the generic power module:
#define INSTANCE() MY
#define ATYPE MYTYPE
#include "GenPower.H"
//
void MY_Multiply(MYTYPE *, MYTYPE);
// End of file


// File MY.C
#include "my.h"
#include "genpower.c"
// Yes we are including the C file, not the H file.
// That way we actually get an implementation of the function
// void MYTYPE_Power(MYTYPE *, int) right here in this module.
// Of course, we also have to provide the multiplication function:
void MY_Multiply(MYTYPE *first_operand_and_result, MYTYPE
other_operand)
{
// Do something multiplicatish.
(*first_operand_and_result) *= other_operand;
}
// End of file


// Test program
#include <stdio.h>
#include "my.h"

What happens if you want to work with more than one type? my.h has
already defined INSTANCE() and MTYPE.
int main(int argc, char* argv[])
{
MYTYPE a;
a = 3;
MY_Power(&a, 5);
printf ("Result of power = %d\n", (int) a);
return 0;
}
// End of file
 
D

Dave Vandervies

This post is about how to get generic modules in a C environment. The
aim is to get close to Ada generics. Using C++ is not an option. It
should all run in a simple C compiler.
[...]

Let me know what you think of the idea. Good? Bad? Old?
Complicated? (In that case I agree, but I think it is worth it.)

Unnecessarily complicated.

Comment #1: If you want {C++|Ada|whatever}, you know where to find it.
Comment #2: What's wrong with a qsort-style interface?


dave
 
T

ttl_idiot

What happens if you want to work with more than one type? my.h has
already defined INSTANCE() and MTYPE.

Good question. I get 2 warnings. One for redefining INSTANCE() and one
for redefining ATYPE. But it compiles and gives the correct result. And
I can't #undef them in the end of GenPower.h. Hmm.
 
C

CBFalconer

Dave said:
This post is about how to get generic modules in a C environment.
The aim is to get close to Ada generics. Using C++ is not an
option. It should all run in a simple C compiler.
[...]

Let me know what you think of the idea. Good? Bad? Old?
Complicated? (In that case I agree, but I think it is worth it.)

Unnecessarily complicated.

Comment #1: If you want {C++|Ada|whatever}, you know where to find it.
Comment #2: What's wrong with a qsort-style interface?

Take a look at how hashlib handles generic structures for storing,
access, etc. It needs nothing unusual or questionable.
Dog-standard C.

<http://cbfalconer.home.att.net/download/>
 
W

Walter Roberson

Malcolm McLean said:
Maths operators are a bit of a red herring. It is nice in theory to
implement an average that works on huge integers and complex types. In
practise, it is easy enough to roll your own, because the need doesn't arise
sufficiently often.

Taking an average is something that is easy to get wrong: as soon as
you are working with floating point types (including those buried
in a complex), you need to sort the values before doing the additions,
because otherwise you risk losing precision and risk other numeric
sins.

There are a bunch of common algorithms that people -think-
"are easy enough to roll your own", but really aren't. And so few people
invest in Knuth these days :(
 
M

Malcolm McLean

Walter Roberson said:
Taking an average is something that is easy to get wrong: as soon as
you are working with floating point types (including those buried
in a complex), you need to sort the values before doing the additions,
because otherwise you risk losing precision and risk other numeric
sins.

There are a bunch of common algorithms that people -think-
"are easy enough to roll your own", but really aren't. And so few people
invest in Knuth these days :(
If you are writing a general-purpose, resuable, high quality average routine
then, yes, sorting by magnitude is one thing you can do to ensure maximum
precision.
For the vast majority of applications there is no need to do this, so "roll
your own" can be better.
 
M

Malcolm McLean

CBFalconer said:
Dave said:
This post is about how to get generic modules in a C environment.
The aim is to get close to Ada generics. Using C++ is not an
option. It should all run in a simple C compiler.
[...]

Let me know what you think of the idea. Good? Bad? Old?
Complicated? (In that case I agree, but I think it is worth it.)

Unnecessarily complicated.

Comment #1: If you want {C++|Ada|whatever}, you know where to find it.
Comment #2: What's wrong with a qsort-style interface?

Take a look at how hashlib handles generic structures for storing,
access, etc. It needs nothing unusual or questionable.
Dog-standard C.

<http://cbfalconer.home.att.net/download/>
Take a look at my simulated annealing code
http://www.personal.leeds.ac.uk/~bgy1mm

it is also generic, but I am not happy with it. The problem is that the
caller has to know how to implement all the functions, and what to do with
the global pointer. All very fiddly. Obviously I want anneal(void *obj,
double (*score)(void *), void (*mutate)(void *)), not lots of housekeeping
parameters.
 
T

ttl_idiot

This post is about how to get generic modules in a C environment. The
aim is to get close to Ada generics. Using C++ is not an option. It
should all run in a simple C compiler.

[...]

Dave said:
[...]
Comment #2: What's wrong with a qsort-style interface?

It is not close to Ada, and the style only works for functions, not for
modules. (?)

I don't like function pointers. Anyway, the hardware we use makes it
impossible to use function pointers. It has to do with fixed adresses
for variables that would normally be on the stack, and a very small
RAM.
English was never my strong point.
--Richard Heathfield in comp.lang.c

Mine either.
 
D

Dave Vandervies

This post is about how to get generic modules in a C environment. The
aim is to get close to Ada generics. Using C++ is not an option. It
should all run in a simple C compiler.

[...]

Dave said:
[...]
Comment #2: What's wrong with a qsort-style interface?

It is not close to Ada, and the style only works for functions, not for
modules. (?)

If you want Ada, you know where to find it.

I don't like function pointers.

If you want Java, you know where to find it.
Anyway, the hardware we use makes it
impossible to use function pointers. It has to do with fixed adresses
for variables that would normally be on the stack, and a very small
RAM.

In that case, it might be worth your time to write (or find) a code
generator that instantiates the generics you need for the types you need
them for, and feed the output of that to your C compiler.

Or you could use a C++ compiler and treat it as C-with-templates. If you
can find one that targets embedded platforms, it can probably even be told
to generate code as if it were C-with-templates and not full-blown C++.

Or you could find an Ada compiler and use *real* Ada generics instead
of trying to fake it in a language where it's a foreign idiom.


dave
 
B

Ben Pfaff

This post is about how to get generic modules in a C environment. The
aim is to get close to Ada generics. Using C++ is not an option. It
should all run in a simple C compiler.

When I think of a generic module, it is something that can be used
exactly as it is. The generic H-file and/or C-file, should not have to
be edited AT ALL in order to use it. If it has to be changed in just 1
line, it could possibly still have great reusability properties, but it
is not generic in the sense I mean.

I recently created a simple module for circular deques that might
fit your criteria. Here's the header file (there's no .c file).
I don't know whether it generalizes naturally to more complicated
data structures, because I've only tried it with this one so
far. It depends on a few project-specific functions, but I'm
sure you can fill in the blanks:

/* PSPP - computes sample statistics.
Copyright (C) 2007 Free Software Foundation, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA. */

#ifndef LIBPSPP_DEQUE_H
#define LIBPSPP_DEQUE_H 1

#include <assert.h>
#include <stdbool.h>
#include <stddef.h>

#include <libpspp/compiler.h>

#include "xalloc.h"

/* Declares data and functions for a deque whose elements have
the given ELEMENT_TYPE. Instances of the deque are declared
as "struct NAME", and each function that operates on the deque
has NAME_ as a prefix. */
#define DEQUE_DECLARE(NAME, ELEMENT_TYPE) \
/* An instance of the deque. */ \
struct NAME \
{ \
size_t capacity; /* Capacity, which must be a power of 2. */ \
size_t front; /* One past the front of the queue. */ \
size_t back; /* The back of the queue. */ \
ELEMENT_TYPE *data; /* Pointer to CAPACITY elements. */ \
}; \
\
/* Initializes DEQUE as an empty deque that can store at least \
CAPACITY elements. (The actual capacity may be larger and is \
always a power of 2.) */ \
static inline void \
NAME##_init (struct NAME *deque, size_t capacity) \
{ \
deque->capacity = 1; \
while (deque->capacity < capacity) \
deque->capacity *= 2; \
deque->front = deque->back = 0; \
deque->data = xnmalloc (deque->capacity, sizeof *deque->data); \
} \
\
/* Destroys DEQUE, which must be empty. */ \
static inline void \
NAME##_destroy (struct NAME *deque) \
{ \
free (deque->data); \
} \
\
/* Returns the number of elements currently in DEQUE. */ \
static inline size_t \
NAME##_count (const struct NAME *deque) \
{ \
return deque->front - deque->back; \
} \
\
/* Returns the maximum number of elements that DEQUE can hold at \
any time. */ \
static inline size_t \
NAME##_capacity (const struct NAME *deque) \
{ \
return deque->capacity; \
} \
\
/* Returns true if DEQUE is currently empty (contains no \
elements), false otherwise. */ \
static inline bool \
NAME##_is_empty (const struct NAME *deque) \
{ \
return NAME##_count (deque) == 0; \
} \
\
/* Returns true if DEQUE is currently full (cannot take any more \
elements), false otherwise. */ \
static inline bool \
NAME##_is_full (const struct NAME *deque) \
{ \
return NAME##_count (deque) >= NAME##_capacity (deque); \
} \
\
/* Returns the element in DEQUE that is OFFSET elements from its \
front. A value 0 for OFFSET requests the element at the \
front, a value of 1 the element just behind the front, and so \
on. OFFSET must be less than the current number of elements \
in DEQUE. */ \
static inline ELEMENT_TYPE * \
NAME##_front (const struct NAME *deque, size_t offset) \
{ \
assert (NAME##_count (deque) > offset); \
return &deque->data[(deque->front - offset - 1) & (deque->capacity - 1)]; \
} \
\
/* Returns the element in DEQUE that is OFFSET elements from its \
back. A value 0 for OFFSET requests the element at the back, \
a value of 1 the element just ahead of the back, and so on. \
OFFSET must be less than the current number of elements in \
DEQUE. */ \
static inline ELEMENT_TYPE * \
NAME##_back (const struct NAME *deque, size_t offset) \
{ \
assert (NAME##_count (deque) > offset); \
return &deque->data[(deque->back + offset) & (deque->capacity - 1)]; \
} \
\
/* Adds and returns the address of a new element at the front of \
DEQUE, which must not be full. The caller is responsible for \
assigning a value to the returned element. */ \
static inline ELEMENT_TYPE * \
NAME##_push_front (struct NAME *deque) \
{ \
assert (!NAME##_is_full (deque)); \
return &deque->data[deque->front++ & (deque->capacity - 1)]; \
} \
\
/* Adds and returns the address of a new element at the back of \
DEQUE, which must not be full. The caller is responsible for \
assigning a value to the returned element. */ \
static inline ELEMENT_TYPE * \
NAME##_push_back (struct NAME *deque) \
{ \
assert (!NAME##_is_full (deque)); \
return &deque->data[--deque->back & (deque->capacity - 1)]; \
} \
\
/* Pops the front element off DEQUE (which must not be empty) and \
returns its address. The element may be reused the next time \
an element is pushed into DEQUE or when DEQUE is expanded. */ \
static inline ELEMENT_TYPE * \
NAME##_pop_front (struct NAME *deque) \
{ \
assert (!NAME##_is_empty (deque)); \
return &deque->data[--deque->front & (deque->capacity - 1)]; \
} \
\
/* Pops the back element off DEQUE (which must not be empty) and \
returns its address. The element may be reused the next time \
an element is pushed into DEQUE or when DEQUE is expanded. */ \
static inline ELEMENT_TYPE * \
NAME##_pop_back (struct NAME *deque) \
{ \
assert (!NAME##_is_empty (deque)); \
return &deque->data[deque->back++ & (deque->capacity - 1)]; \
} \
\
/* Expands DEQUE, doubling its capacity. */ \
static inline void \
NAME##_expand (struct NAME *deque) \
{ \
struct NAME old_deque = *deque; \
NAME##_init (deque, deque->capacity * 2); \
while (!NAME##_is_empty (&old_deque)) \
*NAME##_push_front (deque) = *NAME##_pop_back (&old_deque); \
free (old_deque.data); \
}

#endif /* libpspp/deque.h */
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,565
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top