Re: Template technicality - What does the standard say?
On Oct 15, 3:06 am, Stephen Horne <sh006d3...@blueyonder.co.uk> wrote:
On Tue, 14 Oct 2008 15:58:15 -0700 (PDT), James Kanze
<james.ka...@gmail.com> wrote:
In some cases of inheritance (particularly where virtual
inheritance is involved), the layout is dynamically
defined;
I can see that there is a potential problem with a macro
expansion implementation (rather than GCCs wrapper
approach) to offsetof, since doing the field reference
would access the virtual table/whatever of an object that
doesn't exist.
Even so, the layout of the class isn't dynamically defined,
it's just dynamically accessed.
Sorry, but when virtual inheritance is involved, it's
dynamically defined. At least with all of the compilers I
know, and I don't think it's possible to implement it
otherwise.
I'm sorry, but it's basic textbook. The compiler determines
what is inherited and places each inherited class at a
particualar offset within the derived class, as if it were
member data (which it is).
You must be reading the wrong textbooks (or you don't understand
virtual inheritance), since it's provably impossible to do so.
And it's simple to try:
#include <iostream>
struct VB
{
virtual ~VB() {} ;
int vb ;
} ;
struct M : virtual VB
{
int m ;
void f( std::ostream& s )
{
// Equivalent of offsetof, but legal everywhere,
// since we have an actual object...
s << ((char*)&vb - (char*)this) << std::endl ;
}
} ;
struct D : M
{
int d ;
} ;
int
main()
{
M v1 ;
v1.f( std::cout ) ;
D v2 ;
v2.f( std::cout ) ;
return 0 ;
}
I get different values for the two variables with every compiler
I try.
Every inherited class gets a fixed offset,
Impossible when virtual inheritance is involved.
thus every inherited member gets a fixed offset (ignoring
statics etc). Those offsets may not apply for classes that
inherit this derived class, due to the sharing aspect for
virtual inheritance, but that's besides the point - right at
the start I said it was necessary to name the right
struct/class.
We are dealing with a class/struct and a member. No dynamic
issues as there is no dynamic object in the first place.
See the above. Your statements and actual tests don't match.
The obvious solution is to not use the traditional macro
expansion for offsetof, and just provide the actual offset
of the field using a builtin.
The problem is that you can't provide it as a constant
expression if there is virtual inheritance.
Yes you can, as mentioned above. The problem is that you're
imagining casting-through-the-class-hierarchy issues where
none exist.
The problem is that I understand virtual inheritance, and know
what is possible, and what isn't.
Every class/struct has one layout defined at compile-time
irrespective of what inheritance features it uses or anything
else.
That's false.
Back at you.
Except that I can prove my statements. Both by example and by
formal proof.
If the libraries can do something, I should be well within
my rights to develop alternative libraries.
Actually, you could legitimately argue just the opposite. If
there's no way of implementing something in the standard
language, you must offer something in the library which does
it. Thus, I/O, memory management, and in the next version,
threads.
Standard libraries in particular should not need to be
implemented using non-standard features.
So how do you propose implementing <iostream> (or <stdio.h>)?
Or the function "time()"? Or malloc() and free()? Things
that can't be implemented using standard features must be
provided by the library. That's a basic principle, and has
been since C.
You know damn well what the context is here,
But you apparently don't.
and you know damn
well that Stroustrup and others have made a big deal of the
supposed fact that the C++ standard library *provides* tools,
but only as one choice. That they are explicitly acknowledged
to be not perfect for everything.
Yes. And I know that the reason offsetof was added to the C
standard, and the only reason, was because it wasn't
implementable in the language. There are good arguments why
things like vector (or strcpy in C) should be implementable
within the constraints of the language, and that's what
Stroustrup was talking about. But that's not all of the
library, and other parts (some basic I/O, memory management,
threading, etc.) are in the library largely because they can't
be implemented in the standard language. That's the context of
offsetof.
There is nothing about a container library that inherently
requires you to look outside of the language for the necessary
tools. If C++ cannot allow programmers to develop containers
without requiring non-standard features, it's a joke.
But that's a different context than offsetof. As far as I know,
none of the implementations of any of the standard containers in
C++ use any non-standard language features. (You can easily
look at the sources for g++, and determine yourself. For that
matter, because they are templates, and most compilers don't
implement export, you can generally look at the sources for any
of the implementations. If you find something non-standard, I'd
be interested in hearing about it.)
Part of the reason they can do this, of course, is that there
are lower level library functions offering access to behavior
that can't be implemented in the standard language---std::vector
uses an allocator which by default uses operator new() which
typically uses malloc. Which can't be written in standard C++,
which is why malloc is in the library.
Why should you have the right to find the offset of a data
member, period?
Because without it, C++ is not fit for purpose, period.
Because you seem to be the only person who needs this
functionality, period. Everyone else implements component
libraries, even very low level component libraries, without it.
References could also cause problems. In general, it would
doubtlessly be possible to loosen the rules somewhat. Doing
so would require a fairly detailed analysis, however, to
ensure that the new rules didn't cause any problem for
potential implementations, and no one on the committee felt
the effort was worthwhile. (The case of POD was "established
practice", from C.)
And therefore likely to be used in container template
libraries, obviously, where the applications contained data
may end up as non-pod, thus implying that the data structure
nodes that end up containing it become non-POD too.
??? I don't understand what you're trying to say. There's
certainly no need of offsetof in a container.
Rubbish, but beside the point.
If you explicitly encourage people to bring C code and
skillsets into C++, you cannot complain when they do so.
There's nothing "skill" in C code which uses offsetof. It was
added on a whim, not because it serves any useful purpose. (I
was able to implement a complete standard C library without it.)
[...]
I was asking for rationale as well, IIRC, but only if you know
it. I'm not holding you responsible except to the extent that
you appear to be complicit ;-)
I regret that there's no rationale available as well. (The C
committee did publish one, after C90.) But no one was there who
was willing to invest the necessary time. I wasn't ever active
on library issues; my "specialty" was (and is) basic language
issues.
With regards to C, you might be interested in the rationale
concerning offsetof: http://www.quut.com/c/rat/d1.html#4-1-5.
As I stated above, it is there to provide a portable means of
doing something that can't be done portably otherwise. As for
the "usefulness" they describe, pointers to members work just as
well, but in practice, I've never found it at all useful (and
I've written a lot of code for the context they describe:
interfacing a data base).
(I will acknowledge that it might be useful in implementing a
garbage collector, as they describe. I've generally used other
techniques, however.)
IIRC, one of the basic design principles of C++ is that people
should be able to adapt their C libraries without a complete
rewrite.
I don't believe that anyone considered the possibility that
the C libraries would use templates. (I wonder why?)
A *STANDARD* technique for moving to C++ is to wrap old C code in
templates or other adaptors, with minimal rewriting - which you
clearly know. As you say...
So standard that it doesn't work.
More likely, they do it that way because that's the way the
implemented it in C, some 25 or 30 years ago.
So it is of course blatantly obvious that people will use
templates with old C libraries.
And? Where is the problem. I can instantiate my templates over
existing C struct's without a problem.
Really. And I suppose the modern solution is the member
pointer, which simply doesn't work at all for low level work.
In order to define what the modern solution would be, you first
have to define the problem. As I said, I've yet to find a
problem for which offsetof was a solution.
This is trivial.
I need to reference a field in a struct (not a particular
instance). I control the definition of the struct, but some
fields take types that are template parameters (and can
therefore be a source of non-PODness) In some cases, these are
the fields I need to reference - but I don't need to reference
(or even know about) members within those template-parameter
types.
Why? That sounds like a torturous solution to a problem I can't
see.
My template is a thin wrapper around non-typesafe data-driven
code. The field-reference needs to be stored as an
initialised value in a static data block - a bit like a
closure.
Doing this with member pointers seems to cause compilers to
choke - they don't want to treat the member pointer as a
constant expression for whatever reason each particular
compiler comes up with, that reason varying from compiler to
compiler depending on the compromises they made with member
pointer implementation.
It would be interesting to see the code which causes the
compiler to choke with member pointers. I have instantiated
templates using pointer to member functions, with no problems.
And as you say, pointer to member syntax is not simple, so
there's a distinct possibility that you got something wrong
there.
Hence offsetof, which is a simple, easy to use way around this
issue and - once you're confident of your access functions -
perfectly safe too.
At least until someone decides to restrict it.
There was no decision to restrict it; it's always been
restricted. There was no one who thought it worthwhile to do
the work necessary to loosen the restrictions.
*Maybe* compilers are better at handling member pointers now,
but not in my experience as of yet (bare in mind that I've
just scratched the surface with GCC - other than that my most
recent compilers are VC++ 2003 and Borland C++ 5.02) and in
any case they certainly weren't working when I was writing the
initial code for these containers .
It's well known (and I beleive, even documented somewhere) that
VC++ requires the -vmg option for pointers to member functions
(and possibly pointer to member data) to work correctly. I've
used pointers to member functions with g++ since 2.31.x, and
never had any problems with them; I've instantiated templates
with them since 2.95.2.
Having written parts of OS kernel code in C++, a garbage
collector in C++, marshalling and demarshalling code in C++,
and who knows what all else, I can say from experience that
you don't know what you're talking about. You do need a few
casts---it's normal that more dangerous actions are readily
visible---but C++ is quite effective at this level.
Can you define the rules in which casting from one member
pointer type to another is safe, independent of any one
compiler? I'll bet you can't because these casts are
explicitly undefined.
Can you find a case where one would reasonable want to cast from
one member pointer type to another? (There are safe casts,
which can be done with static_cast. And this is fairly well
defined in the standard.)
An experiment that we now can't get rid of, like exception
specifications on function declarations, which are just as
broken (your black boxes are no longer black, and even
nested black-boxes cannot be replaced, many layers down,
without probably violating all the exception specifications
running through layers of yours and other peoples code) but
at least no-ones trying to force anyone to use them.
Actually, empty exception specifications are very useful.
They're used in the standard at times, for a reason.
Empty exception specifications are a special case which has
value, but that's not a justification for the full exception
specification part of the language. Sometimes, extending and
generalizing a good idea is a bad idea.
It's a problem when you innovate. It's hard to know in advance
what will or will not be needed. On the whole, the C++
committee seems to have gotten it better than most other
languages.
Offsetof is not needed. The proof: no one is using it, even
in C.
By your own argument, people have used it in some pretty
important libraries in C, which have now become C++ libraries.
Name one.
Ergo it is used in both C and C++. In standard library
implementations, no less.
I've never seen it. And I've implemented a complete C standard
library (a long time ago).
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34