On Tuesday, 16 February 2016 at 06:04:42 UTC, Jonathan M Davis
wrote:
On Monday, 15 February 2016 at 22:48:16 UTC, Walter Bright
wrote:
rears its head again :-)
Head Const is what C++ has for const, i.e. it is not
transitive, applies to one level only. D has transitive const.
In this particular case, doesn't this really just come down to
mangling? It's undefined behavior in D to mutate a const object
(even if it was constructed as mutable), so we can't just slap
D const on C++ types and have that work, since the C++ code
could legally mutate the object. But since C++ considers it
defined behavior to mutate a const object by casting away const
(or at least it does in all but some very specific cases), can
we just get away with the D code treating D const as mutable?
If so, then it's purely a matter of mangling. And for that, we
could do something like add @cppconst or cppconst that the
compiler recognizes and which is only valid in extern(C++)
declarations. It would unfortunately have to be more than a
simple attribute, because it would have to apply to parts of a
parameter's type like C++'s const does (instead of the whole
type at once), but if it were implemented such that it was just
something that went on the extern(C++) function parameter types
(or return types) for mangling purposes, and the D code just
treated it as mutable, then it would at least restrict the muck
to extern(C++).
Actually, one could argue that the fact that we support const at
all with other languages - even with C - is broken in that C/C++
code can cast away our const and mutate the variable we passed
in, breaking our type system. We rely on the programmer to know
that the C/C++ code isn't going to do that, and most of the time,
you really have no clue. You just assume that it won't - and it
probably won't, but you don't know for sure.
So, most of the time, it will work just fine, but const with
extern(C) and extern(C++) really does not provide the guarantees
that const is supposed to provide.
Of course, there's also the issue that with extern(C) and
extern(C++), everything is really __gshared, and yet we typically
treat it like it's thread-local just like the D code is.
Arguably, all parameters and return types with extern(C) and
extern(C++) should be automatically treated as shared, and not
doing so is risking a world of pain if the C/C++ code misbehaves,
but having them be treated as shared would be really annoying to
deal with, and most C/C++ code that's being called probably isn't
doing anything with threads that could violate D's guarantees
about thread-local variables (just like most C/C++ code
presumably isn't casting away const and mutating when we pass it
something from D) - but there's no guarantee that it isn't.
So, as far as compiler guarantees and the type system go,
extern(C) and extern(C++) are a bit of a disaster, much as they
generally work in practice. But I don't know what we can really
do about that other than trying to educate people about the risks
and possibly have the compiler avoid certain types of
optimizations around extern(C) and extern(C++) code (which it may
already do).
- Jonathan M Davis