Portable repulsive type extension ?

A

Arnaud Legrand

Hello,

I have a question about portability and I have not found the answer in the
FAQ. I have different modules to build. All of them have the same private
part and a common public part (though the implementation in all cases may be
different). Note that I already have implemented a similar idea and that it
works pretty well with my gcc but I don't know whether it is portable or not
(especially with those damn padding bytes... ;). As a tiny example is better
than a long explanation let me explain you how I actually write it (this is
a brain dead example but it is to explain the idea):

#define PUBLIC \
int (*get)(void* self); \
void (*reset)(void* self)

#define EXTENSION_PLUS \
void (*inc)(void *self); \
void (*dec)(void *self)

#define EXTENSION_TIMES \
void(*double)(void *self); \
void(*divide_by_two)(void *self) \

I know these defines are ugly. That is one of the other reason why I'm
asking beside portability.

With these macros, I can define two different modules with the following
API:

typedef struct {
PUBLIC;
EXTENSION_PLUS;
} s_counter_t, *counter_t;
counter_t counter_new(void);
void counter_free(counter_t c);

and

typedef struct {
PUBLIC;
EXTENSION_TIME;
} s_exponent_t, *exponent_t;
exponent_t exponent_new(void);
void exponent_free(exponent_t e);

In fact, both implementation of these modules include the following common
definition:

#define PRIVATE \
int value
typedef struct {
PRIVATE;
} s_private_common_part_t, *private_common_part_t;

In counter.c, one would find

typedef struct {
PRIVATE;
PUBLIC;
EXTENSION_PLUS;
} s_private_counter_t, *private_counter_t;

counter_new() actually creates a private_counter_t

-------------------------------------
| PRIVATE | PUBLIC | EXTENSION_PLUS |
-------------------------------------
^
|_________
but returns a pointer to here |, i.e. a valid counter_t.

Likewise, in exponent.c, one would find:

typedef struct {
PRIVATE;
PUBLIC;
EXTENSION_TIMES;
} s_private_exponent_t, *private_exponent_t;

exponent_new() would actually creates a private_exponent_t

--------------------------------------
| PRIVATE | PUBLIC | EXTENSION_TIMES |
--------------------------------------

Both module access their PRIVATE values by accessing memory a little bit
before their argument. The nice point with such a way of defining these
modules is that you can then use them seamlessly like that :

counter = counter_new();
exponent = exponent_new();

counter->reset(counter); /* set to 0 */
counter->inc(counter); /* ++ */
counter->get(counter); /* returns 1 */

exponent->reset(exponent); /* set to 1 */
exponent->double(exponent); /* *=2 */
exponent->get(exponent); /* returns 2 */

In this example you always have to give exponent as an argument but in my
particular case, there is always a unique object for each module (at most
one counter and one exponent) and the arguments are generally some other
abstract datatypes (void * is the correct C word for polymorphism, right?;).

OK. Now that I have proved you that I knew how to write something ugly, let
me precise that before writing it like that, I were using struct definitions
instead of these #define. But I had to write :

exponent->public.reset(); /* set to 1 */
exponent->extension.double(); /* *=2 */
exponent->public.get(); /* returns 2 */

Even though the version with whole structures is cleaner and definitely
portable, I think that the one I have just written is really convenient for
users (no need to wonder whether you are using a basic functionality or a
extension).

Thanks for reading until here. Any suggestion ?

Arnaud
 
S

S.Tobias

I'm not quite sure what your question is and how I could help you,
but I'll write a few remarks, maybe they'll clarify some things
for you.


[snippings at different places]

A global remark: `double' is a keyword in C and you cannot
use it as a name for function, variable, member, or anything else.
Let's pretend that `double' is really `Double'.

With structs you can count on two guarantees:
1. First member is at offset 0;
2. In all structs common initial member sequence has the same layout.
typedef struct {
PUBLIC;
EXTENSION_PLUS;
} s_counter_t, *counter_t;
counter_t counter_new(void);
void counter_free(counter_t c);
typedef struct {
PUBLIC;
EXTENSION_TIME;
} s_exponent_t, *exponent_t;
exponent_t exponent_new(void);
void exponent_free(exponent_t e);
typedef struct {
PRIVATE;
} s_private_common_part_t, *private_common_part_t;
typedef struct {
PRIVATE;
PUBLIC;
EXTENSION_PLUS;
} s_private_counter_t, *private_counter_t;
counter_new() actually creates a private_counter_t

Here's a problem: you create s_private_counter_t, but you return
a pointer of type `pointer to s_counter_t' which points to PUBLIC.
First of all, such access via this pointer is Undefined by itself;
second, layouts of PUBLIC+EXTENSION_PLUS in s_counter_t and
s_private_counter_t may be different.
typedef struct {
PRIVATE;
PUBLIC;
EXTENSION_TIMES;
} s_private_exponent_t, *private_exponent_t;
exponent_new() would actually creates a private_exponent_t
Both module access their PRIVATE values by accessing memory a little bit
before their argument.

For s_private_counter_t and s_private_exponent_t (and
s_private_common_part_t) you can only count on layout of PRIVATE to be
the same.

Suppose the structures were re-written like:
typedef struct
{
PRIVATE;
s_counter_t counter_member;
} s_private_counter_t;
and counter_new() returned pointer to counter_member.

The offsets of PUBLIC in s_private_counter_t and s_private_exponent_t
might be different. This would be not a problem if you knew the
type, but...
counter = counter_new();
exponent = exponent_new();
counter->reset(counter); /* set to 0 */
counter->inc(counter); /* ++ */
counter->get(counter); /* returns 1 */
exponent->reset(exponent); /* set to 1 */
exponent->double(exponent); /* *=2 */
exponent->get(exponent); /* returns 2 */

....you might also want to have:
typedef struct { PUBLIC; } s_generic_t;
s_generic_t *generic = (s_generic_t*) counter;
generic->get(generic);
Without knowing the real type of the object _into_ which `generic'
points, you couldn't access `value' in PRIVATE (besides that
it would still be UB).
abstract datatypes (void * is the correct C word for polymorphism, right?;).

<speculation>

"Polymorphism" could only be simulated in C, but implementing this is
just jumping from one kind of ugliness to another.

typedef int (*get_f)(void*self);
/* get() members are to be virtual */
struct Base1 { int value; get_f get; };
struct Base2 { int value; get_f get; };
struct Derived
{
struct Base1 base1;
struct Base1 base1;
int value;
get_f get;
};
int Derived_get(void*self) { return ((struct Derived*)self)->value; }
Now the constructor for Derived has to initialize all three get-s to
the function Derived_get, and then you can access:
Derived *derived = /*...*/;
Base1 *base1 = (Base1*)derived;
derived->get(derived);
base1->get(base1);
For completeness, we must have constructors for Base* types, Base1_get()
and Base2_get(), and up- and down-casts between Base* and Derived
class^H^H^H^H^Hstruct pointers.

In the "dreaded diamond" type inheritance Base1 structs would have to have
struct SuperBase member. This would not be a true virtual member, because
we would have two copies in Derived; it means that all accessor fns (and
we may access SuperBase *only* through these accessors now) to this
class would have to be "virtual" and point to some Derived_somethings(),
that would have to keep the two copies in synch. Since we might want
every struct to be a virtual-base to something, this would have to
hold true for every struct.

Enough. How that could be nice, I don't dare to argue. The Good News
is that there is already a language that supports polymorphism very well.

exponent->public.reset(); /* set to 1 */
exponent->extension.double(); /* *=2 */
exponent->public.get(); /* returns 2 */

Looks good to me.
Even though the version with whole structures is cleaner and definitely
portable, I think that the one I have just written is really convenient for
users (no need to wonder whether you are using a basic functionality or a
extension).
Thanks for reading until here. Any suggestion ?

C is a simple language and is very good for simple things (most problems
are simple and can be easily expressed in C). If your problem is complex,
then either simplify the problem or use a language that is suitable for
that problem. I think there is no point in re-inventing C++ in C, it
solves nothing and makes things more difficult than they need to be -
unless it's a mental exercise and a distraction in itself.

(I'm sorry for a too long text.)
 
M

Merrill & Michele

"S.Tobias"
[snipped heavily]
A global remark: `double' is a keyword in C and you cannot
use it as a name for function, variable, member, or anything else.
Let's pretend that `double' is really `Double'.

This gets back to identifiers. If only for the case difference you'd be in
trouble, whereas coconuts and olives never offend and can be replaced at the
end of development by words that guide the thinking of the next person who
has to look at the code (even if that is yourself in 5 years).
C is a simple language and is very good for simple things (most problems
are simple and can be easily expressed in C). If your problem is complex,
then either simplify the problem or use a language that is suitable for
that problem. I think there is no point in re-inventing C++ in C, it
solves nothing and makes things more difficult than they need to be -
unless it's a mental exercise and a distraction in itself.

To see polymorphism done in some fashion in C has extraordinary pedagogical
value. It did, however, make me late. MPJ
 
A

Arnaud Legrand

Hello,

A global remark: `double' is a keyword in C and you cannot
use it as a name for function, variable, member, or anything else.
Let's pretend that `double' is really `Double'.

Sure! I never have compiled the previous example and had not paid attention
to that. I was just looking for a simple example to serve as a basis to my
question. Sorry about that.
With structs you can count on two guarantees:
1. First member is at offset 0;
2. In all structs common initial member sequence has the same layout.
OK.

Here's a problem: you create s_private_counter_t, but you return
a pointer of type `pointer to s_counter_t' which points to PUBLIC.
First of all, such access via this pointer is Undefined by itself;

Undefined because of the second point, right ?
second, layouts of PUBLIC+EXTENSION_PLUS in s_counter_t and
s_private_counter_t may be different.

OK. That was part of my question. It took me a little bit of time (though it
was obious after all) to figure out an example where the layout was
different.

#define PRIVATE \
char foo;

#define PUBLIC \
char bar; \
char *blob

In PRIVATE+PUBLIC, we have &pp.blob - &pp.bar = 3 whereas in PUBLIC only, we
have &p.blob - &p.bar = 4 (on my laptop, with gcc 3.3.4 of course, it may be
different somewhere else). I think that in my case, as there are only
pointers in my structs, there would never be any problem but you're right.
It's definitively unsure. Let's keep it simple and stupid (and clean). I'll
use plain structures.
Looks good to me.

Yes and to most people I've been talking about it too. Nevermind, let's
forget about my macros then. :)

Thanks for your comments and your speculation.

Best regards,

Arnaud Legrand
 
S

S.Tobias

Arnaud Legrand said:
On 11/25/04 S.Tobias wrote:

(I should have said: dereferencing this pointer; you can access the object
via this pointer if you cast it properly, eg. to (unsigned char*).)
Undefined because of the second point, right ?

Sort of yes, because you may not count on anything else but that.
s_private_counter_t and s_counter_t don't have a common initial sequence:
PRIVATE + PUBLIC + EXTENSION
PUBLIC + EXTENSION

Yes and to most people I've been talking about it too. Nevermind, let's
forget about my macros then. :)

My intention was not to discourage you; I often do similar things
as you want to do myself - what can be de-uglified, should be. This
is more of a design issue and is not an easy one: first define what
"ugly" and "nice" are. :)

All I wanted was to show errors in your assumptions, not to suggest
any solution.

When writing my answer I had a feeling that you are mentally mixing
two concepts: data hiding, and interfaces (which includes inheritance)
(I think it can be seen in your naming scheme and your attempts).
C doesn't support access rights - what is declared can be accessed.
I think it might help you to look from interfaces POV, for example:
COMMON - documented, all objects (of a group) share it, it may
be common initial sequence (some parts may be
not documented though, but exist in every instance)
EXTENSION - documented, but may change among subgroups
PRIVATE - not documented, may change, only some special library
functions access this
I don't think there is much reason for PRIVATE to exist, but I
might be wrong (it makes more sense to make "private" (undocumented)
parts within COMMON and EXTENSION).
(This of course does not ensure binary compatibility.)
 
P

pete

Arnaud Legrand wrote:
#define EXTENSION_TIMES \
void(*double)(void *self); \
void(*divide_by_two)(void *self) \

I know these defines are ugly. That is one of the other reason why I'm
asking beside portability.

It's impossible to terminate the last line of a macro with a backslash.
(because then, it is no longer the last line)
 

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

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top