473,386 Members | 1,766 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,386 software developers and data experts.

Pointer to "base" type - what does the Standard say about this?

Hi, all!

Before i ask my question, i want to clarify that my question is not
about the code i will show, but about what the C Standard says should
happen.

A week or so ago it occurred to me that one can implement a very basic
form of subclassing in C (the gurus certainly already know this, but
it was news to me). What i've done (shown below) seems to work all
fine and well, and does exactly what i'd expect, but i'm asking about
it because when i switched to a higher optimization level on gcc i
started getting warnings about type-punned pointers violating "strict
mode." That got me wondering, does it mean "strict C mode" or "strict
GCC mode"? i don't care much about the latter, as long as i comply
with the former. To be clear (again), my question is not GCC-specific.
My question is whether or not the approach i've taken here is legal
according to The Standard. i only ask because GCC suggests (at some
optimization levels, anyway) that i might be violating some C rule
without knowing i'm doing so.

The code (sorry for the length - it's about as short as i can make
this example in C while still keeping it readable):

// ------------------- begin code
#include <stdio.h>
#include <stdlib.h>

struct base_type; // unfortunate fwd decl
// Public API for base_type objects:
struct base_public_api
{
void (*func1)( struct base_type const * self );
long (*func2)( struct base_type const * self, int );
};
typedef struct base_public_api base_public_api;

// Base-most type of the abstract interface
struct base_type
{
base_public_api api;
};
typedef struct base_type base_type;

// Implementation of base_type abstract interface
struct sub_type
{
base_public_api api;
int member1;
};
typedef struct sub_type sub_type;

#define MARKER if(1) printf("MARKER: %s:%d:%s():
\n",__FILE__,__LINE__,__func__); if(1) printf

#define SUBP ((sub_type const *)self)
void impl_f1( base_type const * self )
{
MARKER("SUBP->member1=%d\n",SUBP->member1);
}
long impl_f2( base_type const * self, int x )
{
return SUBP->member1 * x;
}

// Now here's the part which is dubious: note the concrete types here:
static const sub_type sub_type_inst = { {impl_f1,impl_f2}, 42 };
static base_type const * sub_inst = (base_type const*) &sub_type_inst;
// ^^^^ "warning: dereferencing type-punned pointer will break strict-
aliasing rules"

int main( int argc, char const ** argv )
{

sub_inst->api.func1(sub_inst);
MARKER("func2()==%ld\n", sub_inst->api.func2(sub_inst, 2) );
return 0;
}
// ------------------- end code

On my box that looks like:
stephan@jareth:~/tmp$ ls -la inher.c
-rw-r--r-- 1 stephan stephan 1184 2008-11-05 14:43 inher.c
stephan@jareth:~/tmp$ make inher
cc inher.c -o inher
stephan@jareth:~/tmp$ ./inher
MARKER: inher.c:34:impl_f1():
SUBP->member1=42
MARKER: inher.c:48:main():
func2()==84
Am i headed down a Dark Path with this approach? Or is there a better/
more acceptable approach to simulating single inheritance in C? (i'm
not abject to changing the model, but i really do need some form of
separate interface/implementation for what i'm doing.)

Many thanks in advance for your insights.

PS (not relevant to the question, really): what's the point of all
that? i'm working on a library where i really need abstract base
interfaces (with only one level of inheritance necessary), and this
approach seems to be fairly clear (though a tad bit verbose at times).
i've used it to implement subclasses of an abstract stream interface,
for example, so my library can treat FILE handles and in-memory
buffers (or client-supplied stream types, with an appropriate wrapper)
with the same read/write API.

PS2: my appologies for the dupe post on comp.lang.c.moderated - i
inadvertently posted to that group.
Nov 5 '08 #1
18 2425
On 5 Nov 2008 at 15:34, Stephan Beal wrote:
Am i headed down a Dark Path with this approach? Or is there a better/
more acceptable approach to simulating single inheritance in C? (i'm
not abject to changing the model, but i really do need some form of
separate interface/implementation for what i'm doing.)
Having subtype as "base class struct + some extra fields" and casting
back to the base when necessary is completely ubiquitous in networking
code based on Berkeley sockets. So if your code is meant to run on
Windows or a *nix that does networking, then this approach will
certainly work.

If your interested in head-on-a-pin discussions about whether it will
work on embedded C for a coffee machine with half the standard library
missing, the "regulars" will no doubt be along soon with their usual
grandstanding answers.

Nov 5 '08 #2
On Nov 5, 5:25 pm, Antoninus Twink <nos...@nospam.invalidwrote:
Having subtype as "base class struct + some extra fields" and casting
back to the base when necessary is completely ubiquitous in networking
code based on Berkeley sockets.
That's good to hear, thanks :).
So if your code is meant to run on
Windows or a *nix that does networking, then this approach will
certainly work.
The code is intended to be platform neutral (but "C" below...), and it
now sounds like the approach i chose is also platform neutral. i was
worried there for a while.

As a baseline i'm trying to make sure it also compiles (and behaves
properly) with tcc (TinyC Compiler).
If your interested in head-on-a-pin discussions about whether it will
work on embedded C for a coffee machine with half the standard library
missing, the "regulars" will no doubt be along soon with their usual
grandstanding answers.
i'm only interested in conforming to The Standard. My programming
won't allow me to sleep at night if i knowingly make use of a compiler-
specific extension (with the exception of a couple very common
extensions, like free placement of var decls in functions, instead of
all at the front).

Thanks for the answer - it sounds like i'm not treading any
particularly dangerous ground here (assuming the API is used properly,
of course).
Nov 5 '08 #3
On Nov 5, 5:44 pm, Stephan Beal <sgb...@googlemail.comwrote:
The code is intended to be platform neutral (but "C" below...), and it
now sounds like the approach i chose is also platform neutral. i was
worried there for a while.
Doh, i spoke to soon:

http://www.cellperformance.com/mike_..._aliasing.html

Says:

"In C99, it is illegal to create an alias of a different type than the
original. This is often refered to as the strict aliasing rule."

Now i'm at an impasse - a few parts of my code rely on C99 features,
all but 1 of them (vsscanf()) i could probably easily do without. But
if i require C99 mode then i'm knowingly using undefined behaviour.
That's a tough call, considering that i don't think i can reasonably
reimplement around this limitation. Damn.
Nov 5 '08 #4
Antoninus Twink <no****@nospam.invalidwrites:
On 5 Nov 2008 at 15:34, Stephan Beal wrote:
Am i headed down a Dark Path with this approach? Or is there a better/
more acceptable approach to simulating single inheritance in C? (i'm
not abject to changing the model, but i really do need some form of
separate interface/implementation for what i'm doing.)

Having subtype as "base class struct + some extra fields" and casting
back to the base when necessary is completely ubiquitous in networking
code based on Berkeley sockets. So if your code is meant to run on
Windows or a *nix that does networking, then this approach will
certainly work.

If your interested in head-on-a-pin discussions about whether it will
work on embedded C for a coffee machine with half the standard library
missing, the "regulars" will no doubt be along soon with their usual
grandstanding answers.
The problem isn't with a coffee machine -- the compiler for which will
probably have a simple optimizer -- but with a complex optimizer which uses
the aliasing rules to drives the optimisation. And so code will
works... until the optimizer see an opportunity to use the fact that two
things shouldn't alias.

In the OP case, there is hope. Replacing

static base_type const * sub_inst = (base_type const*) &sub_type_inst;

by

static base_type const * sub_inst = &sub_type_inst.api;

will remove complains about this line. Now the only problematic part is

((sub_type const *)self)

but this isn't problematic AFAIK (6.7.2.1/14 in C99 and I seem to remember
that C90 has the same language: A pointer to a structure object, suitably
converted, points to its initial member (or if that member is a bit-field,
then to the unit in which it resides), and vice versa.) BTW, that reference
shows that there isn't a need to complains about the first line.

Yours,

--
Jean-Marc
Nov 5 '08 #5
Stephan Beal wrote:
....
// ------------------- begin code
#include <stdio.h>
#include <stdlib.h>

struct base_type; // unfortunate fwd decl
// Public API for base_type objects:
struct base_public_api
{
void (*func1)( struct base_type const * self );
long (*func2)( struct base_type const * self, int );
};
typedef struct base_public_api base_public_api;
You can combine those two statements:

typedef struct base_public_api
{
// details
} base_public_api;
// Base-most type of the abstract interface
struct base_type
{
base_public_api api;
};
typedef struct base_type base_type;

// Implementation of base_type abstract interface
struct sub_type
{
base_public_api api;
int member1;
};
typedef struct sub_type sub_type;
....
// Now here's the part which is dubious: note the concrete types here:
static const sub_type sub_type_inst = { {impl_f1,impl_f2}, 42 };
static base_type const * sub_inst = (base_type const*) &sub_type_inst;
// ^^^^ "warning: dereferencing type-punned pointer will break strict-
aliasing rules"
There's no problem there. Section 6.7.2.1p13 says "A pointer to a
structure object, suitably converted, points to its initial member (or
if that member is a bit-field, then to the unit in which it resides),
and vice versa."

Strictly speaking, you have to apply that rule twice, and that implies
that the correct conversion has to be:

static_base_type const* sub_inst = (base type const*)
(base_public_api*)&sub_type_inst;

If we are given the fact that, for a specific set of types A, B, and
C, where p is object of type C*, the conversion (A*)(B*)p is legal,
the standard says nothing that allows us to conclude that (A*)(B*)p ==
(A*)p. It doesn't even guarantee that (A*)p is also legal. However,
in practice, I would expect this to work, and I believe that the
intent was that it should work.

Nov 5 '08 #6
On Nov 5, 6:36 pm, Jean-Marc Bourguet <j...@bourguet.orgwrote:
The problem isn't with a coffee machine -- the compiler for which will
probably have a simple optimizer -- but with a complex optimizer which uses
the aliasing rules to drives the optimisation. And so code will
works... until the optimizer see an opportunity to use the fact that two
things shouldn't alias.
Optimization is certainly a potential problem. i can conceive that for
some reason a compiler might optimize or pad these differently:

struct sub1
{
base_api api;
int m1;
double m2;
};

struct sub1
{
base_api api;
double m1;
char const * m2;
};

but my knowledge of explicit optimizations done by any given compiler
is pretty minimal. i'm much better versed in C++ than C.
In the OP case, there is hope. Replacing

static base_type const * sub_inst = (base_type const*) &sub_type_inst;

by

static base_type const * sub_inst = &sub_type_inst.api;
That was my original thought, but the point of passing (base_type
[const]*) as the "self" argument of base_type_api was to give me a
level of indirection which i want (for storage of subtype-specific
data without requiring subclasses to literally redeclare the whole
public API), and i lose that (and features based off of it) if i pass
a (base_type_api*) and cast it to a (sub_type*) (which in my eyes is
just plain wrong, even if it might work in this case).
will remove complains about this line. Now the only problematic part is

((sub_type const *)self)

but this isn't problematic AFAIK (6.7.2.1/14 in C99 and I seem to remember
that C90 has the same language: A pointer to a structure object, suitably
converted, points to its initial member (or if that member is a bit-field,
then to the unit in which it resides), and vice versa.)
Coincidentally, i spent the last half hour reading up on that topic.
The reading here was enlighting:

http://www.cellperformance.com/mike_..._aliasing.html

After reading that, i'm very confused about whether using the
"restrict" keyword might in some way help me here.

My current conclusion is:

a) it's technically illegal.
b) it's largely assumed to be safe on "most" platforms.
c) my subclasses are all private implementations (file-level scope)
and are never referenced using their subtype outside of a single file,
so the compiler might have a chance of knowing what i'm hoping for.
BTW, that reference
shows that there isn't a need to complains about the first line.
gcc doesn't complain until i turn on -O2 or higher or turn on the -
fstrict-aliasing flag. tcc doesn't complain at all.

Thanks for your input :).
Nov 5 '08 #7
On Nov 5, 6:47 pm, jameskuyper <jameskuy...@verizon.netwrote:
You can combine those two statements:

typedef struct base_public_api
{
// details

} base_public_api;
Thanks for that. i'm far more used to C++ than C (been away from C
since 1995 or so), so i'm not all caught up yet.
There's no problem there. Section 6.7.2.1p13 says "A pointer to a
structure object, suitably converted, points to its initial member (or
if that member is a bit-field, then to the unit in which it resides),
and vice versa."
AND vice versa? That changes things. So that means that i could cast
the (base_type_api*) itself back to the original subclass which
contains it (provided i use it as we've shown here)?

So does that imply that the following is actually legal? (warning -
uncompiled)

typedef struct API
{
int (*func1)( struct API * );
// ... more API ...
} API;

typedef struct Foo
{
API api;
int val;
} Foo;

int func1_Foo( API *x )
{
return (Foo*)x;
}

....

Foo foo;
foo.api.func1 = func1_Foo;
printf("%d\n",foo.api.func1( &foo ));
If that's legal then i'm happy (though a bit perplexed as to why it
would be allowed).
If we are given the fact that, for a specific set of types A, B, and
C, where p is object of type C*, the conversion (A*)(B*)p is legal,
the standard says nothing that allows us to conclude that (A*)(B*)p ==
(A*)p. It doesn't even guarantee that (A*)p is also legal.
Those last two parts: fair enough, and i think i understand why that
must be so, but it is philosophically unsettling nonetheless.
However,
in practice, I would expect this to work, and I believe that the
intent was that it should work.
MY intent is for it to work, certainly ;). i just hope i'm not
violating anyone's bits by doing this.

Thanks for your insightful reply :).
Nov 5 '08 #8
jameskuyper wrote:
Stephan Beal wrote:
...
// ------------------- begin code
#include <stdio.h>
#include <stdlib.h>

struct base_type; // unfortunate fwd decl
// Public API for base_type objects:
struct base_public_api
{
void (*func1)( struct base_type const * self );
long (*func2)( struct base_type const * self, int );
};
typedef struct base_public_api base_public_api;
....
// Base-most type of the abstract interface
struct base_type
{
base_public_api api;
};
typedef struct base_type base_type;

// Implementation of base_type abstract interface
struct sub_type
{
base_public_api api;
int member1;
};
typedef struct sub_type sub_type;

...
// Now here's the part which is dubious: note the concrete types here:
static const sub_type sub_type_inst = { {impl_f1,impl_f2}, 42 };
static base_type const * sub_inst = (base_type const*) &sub_type_inst;
// ^^^^ "warning: dereferencing type-punned pointer will break strict-
aliasing rules"

There's no problem there.
[Nonsense]

Sorry about that. I got mixed up because I misread your code. I was
somehow got confused while reading your code, and was under the
impression that sub_type_inst had a member of type base_public_api
(which was correct), and that base_public_api had a member of type
base_type (which is exactly backwards). You can only perform such a
conversion by defining a union of both types, and only when the
original pointer points to one of the members of such a union.
Nov 5 '08 #9
On Nov 5, 7:23 pm, jameskuyper <jameskuy...@verizon.netwrote:
Sorry about that. I got mixed up because I misread your code. I was
somehow got confused while reading your code, and was under the
impression that sub_type_inst had a member of type base_public_api
(which was correct), and that base_public_api had a member of type
base_type (which is exactly backwards).
It was a good try, though :).
You can only perform such a
conversion by defining a union of both types, and only when the
original pointer points to one of the members of such a union.
i'm considering the union option. However, that would require that i
use all the concrete subtypes in the public union. Right now my
subtypes are all static/file-scope implementations, so that wouldn't
be possible without revealing those types, some of which require
optional add-on functionality like sqlite3 (which the subclasses hide
in the current API).

In the off chance that you'd like to see the actual code, visit here:

http://fossil.wanderinghorse.net/rep...1n.net/c11n/io

give in the user name "anonymous" with password "anonymous" (that's an
anti-bot feature of the hosting SCM software), click Login, and you'll
see the files. The base types are declared in c11n_io.h (search for
"_api api;") and a couple concrete impls of the c11n_stream base are
in c11n_stream_*.c. (c11n_io_handler_*.c are also relevant but
significantly more complex because they are grammar parser
implementations.)

Again, thanks for the insights!
Nov 5 '08 #10
Stephan Beal wrote:
>
Many thanks in advance for your insights.
The technique you describe has been used in C since forever. The other
posters already gave you a quote form the language specification, which
validates this useful technique, and which was actually included into
the language specification specifically for that purpose.

(The valid struct<->first member conversion actually made it into C++
specification as well).

--
Best regards,
Andrey Tarasevich
Nov 5 '08 #11
On Nov 5, 7:57 pm, Hallvard B Furuseth <h.b.furus...@usit.uio.no>
wrote:
Stephan Beal writes:
Doh, i spoke to soon:
http://www.cellperformance.com/mike_...standing_stric...
Says:
"In C99, it is illegal to create an alias of a different type than the
original. This is often refered to as the strict aliasing rule."

Not quite. What is illegal, with some exceptions, is to access an
<HUGE snip

Wow, thanks for that! Now i've got some reading to do :).

The base struct macro you show is basically how i'm initiaizing my
subclasses, with the exception that i have on extra degree of
indirection - the subtypes hold an object which itself holds the
common API (that approach seems to simplify maintenance of the
subclass implementations in the event of a change in the base API).
Nov 5 '08 #12
Stephan Beal wrote:
On Nov 5, 6:36 pm, Jean-Marc Bourguet <j...@bourguet.orgwrote:
>The problem isn't with a coffee machine -- the compiler for which will
probably have a simple optimizer -- but with a complex optimizer which uses
the aliasing rules to drives the optimisation. And so code will
works... until the optimizer see an opportunity to use the fact that two
things shouldn't alias.

Optimization is certainly a potential problem. i can conceive that for
some reason a compiler might optimize or pad these differently:

struct sub1
{
base_api api;
int m1;
double m2;
};

struct sub1
{
base_api api;
double m1;
char const * m2;
};

but my knowledge of explicit optimizations done by any given compiler
is pretty minimal. i'm much better versed in C++ than C.
Those structs will likely be padded differently, yes, but you _should_
be able to cast from one to the other as long as you only access the
initial elements that they have in common (in this case, only "api").
Compilers are required to pad consistently enough that, as far into the
struct as the element types remain the same, they will be at the same
offsets; this is deliberate to allow casts to access them.

Think about this example:

struct point {
int x;
int y;
};
struct circle {
int x;
int y;
int radius;
};
struct rect {
int x;
int y;
int width;
int height;
};

Any time you need a struct point, you can safely cast a struct circle
and access x or y. This is very, very bare-bones inheritance and
polymorphism.
>In the OP case, there is hope. Replacing

static base_type const * sub_inst = (base_type const*) &sub_type_inst;

by

static base_type const * sub_inst = &sub_type_inst.api;

That was my original thought, but the point of passing (base_type
[const]*) as the "self" argument of base_type_api was to give me a
level of indirection which i want (for storage of subtype-specific
data without requiring subclasses to literally redeclare the whole
public API), and i lose that (and features based off of it) if i pass
a (base_type_api*) and cast it to a (sub_type*) (which in my eyes is
just plain wrong, even if it might work in this case).
You could also do the above as:

struct point {
int x;
int y;
};
struct circle {
struct point center;
int radius;
};

The syntax to access x and y isn't quite as pretty, but the layout in
memory will be the same (a pointer to a struct is guaranteed to be
equivalent to a pointer to its first element) and the compiler should be
quiet if you cast a struct circle to a struct point.

I haven't read all your code due to the length, so I'm not entirely sure
this helps, but I've used the same tricks in OO code of my own.
gcc doesn't complain until i turn on -O2 or higher or turn on the -
fstrict-aliasing flag. tcc doesn't complain at all.
If TCC doesn't complain, it probably doesn't have enough optimizing
intelligence to care about aliasing problems. GCC is pretty aggressive
in that area, but there's a huge cost in complexity to detect aliasing
(or lack thereof), which TCC probably can't afford given its name.

S
Nov 5 '08 #13
Stephen Sprunk wrote:
....
Those structs will likely be padded differently, yes, but you _should_
be able to cast from one to the other as long as you only access the
initial elements that they have in common (in this case, only "api").
Compilers are required to pad consistently enough that, as far into the
struct as the element types remain the same, they will be at the same
offsets; this is deliberate to allow casts to access them.
The relevant section of the standard makes that guarantee only if the
two structs are members of the same union. In practice, it generally
works for a much wider range of cases than the ones guaranteed by the
standard.
Nov 5 '08 #14
Stephen Sprunk wrote:
Stephan Beal wrote:
>>
Optimization is certainly a potential problem. i can conceive that for
some reason a compiler might optimize or pad these differently:

struct sub1
{
base_api api;
int m1;
double m2;
};

struct sub1
'sub2', I think?
>{
base_api api;
double m1;
char const * m2;
};

but my knowledge of explicit optimizations done by any given compiler
is pretty minimal. i'm much better versed in C++ than C.

Those structs will likely be padded differently, yes, but you _should_
be able to cast from one to the other as long as you only access the
initial elements that they have in common (in this case, only "api").
Compilers are required to pad consistently enough that, as far into the
struct as the element types remain the same, they will be at the same
offsets; this is deliberate to allow casts to access them.
[...]
See James Kuyper's response, but the struct layout is not the
only issue. Even if the layouts are as you want them, the compiler
is allowed to "know" that a `struct sub1*' and a `struct sub2*' do
not point to the same thing (see Hallvard Furuseth's response). An
aggressive optimizer might assume that storing to the `api' element
of something pointed at by a `struct sub1*' does not affect the `api'
element of something pointed at by a `struct sub2*', and if in fact
they point at the same memory odd things could happen.

One clean way to handle this is to pack all the allied types
into a union, but this requires that you know all those types up
front, which can be irksome. Another clean way to handle it is to
use a `base_api*' and point it at the `api' element of whichever
structs you're dealing with. Since the `api' is the first element
in each struct, it is always safe to convert the struct pointer to
a `base_api*' and back.

--
Er*********@sun.com
Nov 5 '08 #15
Stephan Beal wrote:
Antoninus Twink <nos...@nospam.invalidwrote:
.... snip ...
>
>If your interested in head-on-a-pin discussions about whether
it will work on embedded C for a coffee machine with half the
standard library missing, the "regulars" will no doubt be along
soon with their usual grandstanding answers.

i'm only interested in conforming to The Standard. My programming
won't allow me to sleep at night if i knowingly make use of a
compiler-specific extension (with the exception of a couple very
common extensions, like free placement of var decls in functions,
instead of all at the front).
Then you should entirely ignore Twink. He is a troll, and only
interested in disturbing the newsgroup.

--
[mail]: Chuck F (cbfalconer at maineline dot net)
[page]: <http://cbfalconer.home.att.net>
Try the download section.
Nov 5 '08 #16
Stephan Beal wrote:
Jean-Marc Bourguet <j...@bourguet.orgwrote:
>The problem isn't with a coffee machine -- the compiler for which
will probably have a simple optimizer -- but with a complex
optimizer which uses the aliasing rules to drives the
optimisation. And so code will works... until the optimizer see
an opportunity to use the fact that two things shouldn't alias.

Optimization is certainly a potential problem. i can conceive that
for some reason a compiler might optimize or pad these differently:
....

The following references may be helpful. The C99 ones are the
standard, while the n869_txt.bz2 is a bzipped version of n169.txt,
which in turn is the last version available as a text file.

Some useful references about C:
<http://www.ungerhu.com/jxh/clc.welcome.txt>
<http://c-faq.com/ (C-faq)
<http://benpfaff.org/writings/clc/off-topic.html>
<http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf(C99)
<http://cbfalconer.home.att.net/download/n869_txt.bz2 (pre-C99)
<http://www.dinkumware.com/c99.aspx (C-library}
<http://gcc.gnu.org/onlinedocs/ (GNU docs)
<http://clc-wiki.net/wiki/C_community:comp.lang.c:Introduction>

--
[mail]: Chuck F (cbfalconer at maineline dot net)
[page]: <http://cbfalconer.home.att.net>
Try the download section.
Nov 6 '08 #17
On Nov 5, 11:07 pm, Eric Sosman <Eric.Sos...@sun.comwrote:
See James Kuyper's response, but the struct layout is not the
only issue. Even if the layouts are as you want them, the compiler
is allowed to "know" that a `struct sub1*' and a `struct sub2*' do
not point to the same thing (see Hallvard Furuseth's response). An
aggressive optimizer might assume that storing to the `api' element
of something pointed at by a `struct sub1*' does not affect the `api'
element of something pointed at by a `struct sub2*', and if in fact
they point at the same memory odd things could happen.
That thought kept me up much of the night :(.
>
One clean way to handle this is to pack all the allied types
into a union, but this requires that you know all those types up
front, which can be irksome. Another clean way to handle it is to
use a `base_api*' and point it at the `api' element of whichever
structs you're dealing with. Since the `api' is the first element
in each struct, it is always safe to convert the struct pointer to
a `base_api*' and back.
Coincidentally, that's the approach was is on my list of trying out
tonight. It requires the fewest changes and "seems" to be safest
(aside from the Union) so far.

:)
Nov 6 '08 #18
A follow up on how i got to a safe solution...

On Nov 6, 7:07 am, Stephan Beal <sgb...@googlemail.comwrote:
On Nov 5, 11:07 pm, Eric Sosman <Eric.Sos...@sun.comwrote:
One clean way to handle this is to pack all the allied types
into a union, but this requires that you know all those types up
i've ruled out the union idea because concrete impls can be provided
by client code, and i obviously can't link those in to my lib.
front, which can be irksome. Another clean way to handle it is to
use a `base_api*' and point it at the `api' element of whichever
structs you're dealing with. Since the `api' is the first element
in each struct, it is always safe to convert the struct pointer to
a `base_api*' and back.
Here's what i've ended up doing, which offers both an approach with
the safety guaranty approach and the extension-which-might-work-but-is-
technically-unsafe approach:

typedef struct base_api {
void (*member1)( struct foo const * self );
int (*member1)( struct foo const * self, int arg1 );
...
void const * implData;
} base_api;

Now my Base type looks like:

typedef struct base {
base_api const * api;
};

(This extra level of indirection isn't really necessary any longer,
and i may get rid of it.)

For my particular cases, all of my implementations can (and in fact
should) be initialized with constant, immutable data (it may be
instance-specific but should be immutable). With this approach i no
longer need concrete "subclasses" - i only need concrete
implementations of base, which allows me to completely avoid the
((base*)mySubT) cast. The impl functions can require that the api-
>implData object be set to some implementation-specific value, which
the impls can then cast to their heart's content.

What's all this for?

As part of c11n (http://s11n.net/c11n/) i need abstract interfaces for
3 particular object types. The interfaces are used by the rest of the
API and only care that impls follow the rules defined in the API docs
for the base class API. For example, i have an interface called
c11n_marshaller, which is a marshaller type for de/serializing objects
of a specific type (we need one implementation/instance per
serializable type). Some common cases (e.g. well-known PODs) can be
combined into a single implementation of the base_api functions,
differing only in the metadata they need for the marshalling
conversion. To do this we point the api->implData to some instance-
specific static struct containing that metadata which differs from POD
type to POD type (e.g. a printf/scanf specifier). For the c11n_stream
interface, the (void * implData) (non-const) member will hold info for
the underlying native stream object (e.g. FILE handle or in-memory
buffer).

Anyway...

Thanks a thousand times to all of you for your feedback - it's helped
me move away from a potentially horrible design mistake!
Nov 6 '08 #19

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

11
by: Joseph Turian | last post by:
Fellow hackers, I have a class BuildNode that inherits from class Node. Similarly, I have a class BuildTree that inherits from class Tree. Tree includes a member variable: vector<Node>...
4
by: Alex Sedow | last post by:
Method invocation will consist of the following steps: 1. Member lookup (14.3) - evaluate method group set (Base.f() and Derived.f()) .Standart say: "The compile-time processing of a method...
2
by: Wade | last post by:
Hi all, We have created some "Base" class pages for our WebForms and UserControls. For instance, when we create a WebForm called "WebForm1.aspx", instead of inheriting from "System.Web.UI.Page"...
7
by: relient | last post by:
Question: Why can't you access a private inherited field from a base class in a derived class? I have a *theory* of how this works, of which, I'm not completely sure of but makes logical sense to...
2
by: reckless2k | last post by:
Client side; knows nothing of Derived: class Base { ... virtual void do_something() } #include "Base.h" void main() {
3
by: Rob | last post by:
I have these classes (elided methods): class Base { public: Base(string name) {...} }; class Derived : public Base {
10
by: Dom Jackson | last post by:
I have a program which crashes when: 1 - I use static_cast to turn a base type pointer into a pointer to a derived type 2 - I use this new pointer to call a function in an object of the...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.