C as a Subset of C++ (or C++ as a superset of C)

N

Nick Keighley

C isn't subset of C++.
C++ isn't superset of C.
They are different.
C++ is a new language.

Now I somewhat agree with a similar opinion thrown long time ago:

  C++ has no relationship with C except its name stolen from C

which is plainly nonsense
 
C

Ceriousmall

lovecreatesbeauty said:
C isn't subset of C++.
C++ isn't superset of C.
They are different.
C++ is a new language.

Now I somewhat agree with a similar opinion thrown long time ago:

C++ has no relationship with C except its name stolen from C

Pure rubbish here, out of what was C with classes born....?
 
J

James Kuyper

On 08/26/2012 10:49 PM, lovecreatesbeauty wrote:
....
Now I somewhat agree with a similar opinion thrown long time ago:

C++ has no relationship with C except its name stolen from C

Nonsense. Almost every feature of C90 is a feature of C++98, and many of
the features added to C99 have since become part of later versions of
C++. C++ has a large number of additional features, and modifies the
meaning of some constructs that are permitted by both languages, but
it's not difficult to write code that avoids all of the
incompatibilities. It is very difficult to write strictly conforming C
that cannot be converted into C code that compiles without change of
meaning as C++ code.

In fact, many people who claim that they have C++ experience have in
fact only used C++ to compile code that would (with at most minor
modifications) compile with exactly the same meaning as C code. This can
actually be a problem if you're trying to hire someone to work on code
that makes extensive use of the features C++ has that C doesn't.
 
J

Jens Gustedt

Am 27.08.2012 09:43, schrieb David Brown:
I understand this (and in fact knew it was the case). But that doesn't
stop it being silly!

The C committee should learn - as the C++ committee apparently knows -
that backwards compatibility is only "important", not "critical". It is
/okay/ to introduce a new "static_assert" keyword in the new version of
a language, as long as people can continue to use their compilers with
older standards. This is not a new idea - consider K&R style function
declarations, for example.

This is your opinion and the C committee seem to have a different
one. Calling it silly is not very constructive, in any case it is not
going to change soon, I guess.

Important fact is that for these features code will equally well
compile in both frameworks.
Failing that, what is to stop the C++ people following the C people here?

Anyway, this is just one example that is easy to see, where the two
committees added the same feature to the two languages but with
differing details.

The more annoying issue, as I see it, is the increasing gap of C
features that are missing in C++. There are steadily more features
being added to C for embedded use, such as fixed point formats. But C++
doesn't get that sort of thing, because it could all be implemented in a
class. I understand the principle that you should not add something to
the core library if it can be done just as well in a class - but the C++
committee doesn't seem to realise that compatibility with C is important
here.

I completely agree on that point, and BTW there are equal points in
the other direction from C++ to C.

Best example for something completely trivial is "{}" as a default
initializer. What the hell does C gain by imposing that there is at
least a 0 in it à la "{0}" ?

Or that C adopted declarations in "for" loops but doesn't allow
"static" declarations or "enum" declarations, and doesn't allow
declarations in "if" or "while" ?

These things look just as if somebody was voluntarily seeking to
introduce incompatibilities, instead of reducing them.

Jens
 
J

Jens Gustedt

Am 27.08.2012 10:40, schrieb David Brown:
If someone is making a wish list here, I'd add support for defining
endianness (allowing code to explicitly define and use variables and
types as big endian or little endian independently of the architecture)

and bit field ordering.

I think this is more strictly defined for C already than for C++,
perhaps the definition that C has would be sufficient for your needs?
That would be great for portable code.

Support for types bigger than "char" which are "can always alias" would
be a big help whenever you need to move data around, and an 8-bit type
that does not alias would be nice for small systems.

I am not sure that I understand this one. C has the optional uintXX_t
that are guaranteed to have a fixed width no padding etc, if they
exist. Would it for your purpose just suffice to make some of these
types mandatory e.g for 8, 16, 32 and 64 bit integers?
Proper, standardised memory barriers of different types and granularity
would be a help for some low-level programming.

My understanding is that these are included in C11 atomics, and since
this part basically identical to C++11, it should be there, too.
Generally speaking, it would be nice to be able to do more C++ in
low-level and embedded code, and cut out some more of the last remnants
of assembly that is still needed.

The atomics extension (optional in C11) should already do a lot in
that direction.

Jens
 
B

BGB

Le 26/08/12 11:57, Ansel a écrit :
Isn't it a lame use of human time and effort to maintain completely
separate
C and C++ standards? As in the words of Betty White about Facebook: "It
[...]

sizeof('b') is sizeof(int) int C, 1 in C++

Which one would you compromise on?

You do not know what you are talking about dude...

Simple. Require sizeof(int)==1. Problem solved. :)

wouldn't work out well or make much sense on most common architectures.

better would probably be: pick one way or the other.
 
L

lawrence.jones

Jens Gustedt said:
on my wish list would be

- variably modified types (not necessarily VLA but pointers to VLA are
nice)

What good are pointers to VLAs without VLAs? What would they point
to???
 
J

Jens Gustedt

Am 27.08.2012 22:50, schrieb (e-mail address removed):
What good are pointers to VLAs without VLAs? What would they point
to???

There is a simple idiom to deal with large matrices

double (*A)[m][n] = malloc *A;

You can easily handle such matrices with functions

void product(size_t n, size_t m, size_t k, double (*A)[n][k], double
(*B)[k][n], double (*C)[n][m]);

All those *A, *B, *C carry all there sizes with them so you can access
elements as (*A)[j] etc

If you allocate all those matrices with malloc as above, no danger of
stack overflow.

Admittedly the (*A) notation is not the most comfortable, but works.
C++'s references would be the perfect complement for that and would make
that idiom really useful.

Jens

http://gustedt.wordpress.com/2011/01/09/dont-be-afraid-of-variably-modified-types/
 
K

Keith Thompson

What good are pointers to VLAs without VLAs? What would they point
to???

I suppose you could permit VLAs with allocated storage duration
but not with automatic storage duration. (They're already not
permitted to have static storage duration.) That would remove the
case where you can define a VLA, but there's no way to handle an
allocation failure. It would also remove the case where they're
most convenient.
 
C

Casey Carter

IMHO:
C++ could adopt C99 or C11 as its C subset (and make "void *" support
implicit conversion to other pointer types, formally allow "#include
<stdio.h>" and friends, adopt the "((void *)0)" definition of NULL, ...);

C++ _does_ formally allow "include <stdio.h>". <XXX.h> defines the same
set of entities as <cXXX>, except that <cXXX> provides them in namespace
std (and optionally the global namespace), whereas <XXX.h> provides them
in the global namespace (and optionally in namespace std). See 17.6.1.2
and Appendix D.5 in C++11.

Definition of NULL as ((void*)0) is contingent on the implicit
conversion of void* to other pointer types.

That implicit conversion from void* to any pointer type is a dangerous
hole in the type system. I admit I'm not much of a C programmer anymore:
the only valid reason I can think of to have this conversion is so that
malloc calls don't require you to specify the type _three_ times (e.g.,
int* p = (int*)malloc(sizeof(*p)); ).

If I really wanted to pitch the committees for stronger ties between C
and C++, I'd probably suggest that (a) C pick up nullptr (and nullptr_t)
to serve as generic null pointer constant, (b) C drop the implicit
conversion from void*, and (c) both languages use a new distinct type
that can implicitly convert to any pointer type. Holes in the type
system are very useful at the fringes of C/C++ programs, I think that
having distinct "source" (X -> any*) and "sink" (any* -> X) pointer
types preserves the utility of C-style void* while minimizing the danger.
 
B

BGB

C++ _does_ formally allow "include <stdio.h>". <XXX.h> defines the same
set of entities as <cXXX>, except that <cXXX> provides them in namespace
std (and optionally the global namespace), whereas <XXX.h> provides them
in the global namespace (and optionally in namespace std). See 17.6.1.2
and Appendix D.5 in C++11.

fair enough, I had thought it was more like, "cwhatever" was the
"standard" way in C++ land, and "whatever.h" was "just something that
happens to work".

admittedly, I have not really dug much into the C++ standard (I have
looked considerably more into the C standard here).


Definition of NULL as ((void*)0) is contingent on the implicit
conversion of void* to other pointer types.

well, yes, but I listed that they could add this as well.
probably most C++ code wouldn't notice the change.

possibly, the change could be limited to 'extern "C"' code or similar
(and/or could be ignored in cases of templates or overloading, or at
least be a lower priority than any other matches).

That implicit conversion from void* to any pointer type is a dangerous
hole in the type system. I admit I'm not much of a C programmer anymore:
the only valid reason I can think of to have this conversion is so that
malloc calls don't require you to specify the type _three_ times (e.g.,
int* p = (int*)malloc(sizeof(*p)); ).

there are plenty of uses besides malloc, so malloc is just one of many
cases (mmap, memcpy, VirtualAlloc, ...).

or, a biggie:
for any user-defined code which accepts or returns "void *".

for example, to do similar tasks in C++ often requires a larger number
of casts.

If I really wanted to pitch the committees for stronger ties between C
and C++, I'd probably suggest that (a) C pick up nullptr (and nullptr_t)
to serve as generic null pointer constant,

could work...

(b) C drop the implicit
conversion from void*,

this would break large amounts of C code.

I don't think people on either side would want changes which cause their
existing programs to break and have to be rewritten.

and (c) both languages use a new distinct type
that can implicitly convert to any pointer type.

we call this 'void *', where this is nearly the only thing that can
really be done with this type anyways ("void" variables are otherwise
pretty much useless, ...).

Holes in the type
system are very useful at the fringes of C/C++ programs, I think that
having distinct "source" (X -> any*) and "sink" (any* -> X) pointer
types preserves the utility of C-style void* while minimizing the danger.

yes, but the least impact route would be to just pick up C's "void *"
semantics for this, since C++ code isn't likely to notice, and existing
C code can keep working unmodified.
 
J

Jens Gustedt

Am 28.08.2012 01:15, schrieb Keith Thompson:
I suppose you could permit VLAs with allocated storage duration
but not with automatic storage duration. (They're already not
permitted to have static storage duration.) That would remove the
case where you can define a VLA, but there's no way to handle an
allocation failure. It would also remove the case where they're
most convenient.

I just don't agree on the last phrase. Pointers to VLA are very
convenient in function interfaces.

Jens

http://gustedt.wordpress.com/2011/01/09/dont-be-afraid-of-variably-modified-types/
 
J

Jens Gustedt

Am 28.08.2012 05:34, schrieb BGB:
we call this 'void *', where this is nearly the only thing that can
really be done with this type anyways ("void" variables are otherwise
pretty much useless, ...).

yes!

That said, I don't really see the point of "void*" as it is in C++
nowadays. Since you'd have to do an explicit conversion any time you
use it, in C++ it has nothing that couldn't be done with a "unsigned
char*". In the contrary, "unsigned char*" allows byte based pointer
arithmetic and to inspect the contents on a byte base.

The only reason, I think, for "void*" is interface compability to
C. Somehow it misses the whole point of it.

Jens
 
J

Jens Gustedt

Am 28.08.2012 10:21, schrieb David Brown:
Basically, I just want to be able to mix and match C and C++ code
freely. I want to be able to write in the same style in each language.
If I am not using C++ features, I want the code to be compilable as C
or C++, with the same functionality each time. And I want to be able to
do so without artificially restricting the features I can use in C. It's
okay if I have to #include a few standard headers, and put in a few
extern "C" wrappers.

Am I really asking too much?

personnaly, I don't think so, but seen in the historical context, most
likely.
(Here's another thing C should support - it should accept extern "C".)

Ah, yes, a good one.

Sometime the next days, if and when I find the time, I'll probably
start to compile a list of these things. Or better three list

- C features missing in C++
- C++ features missing in C
- points where both should (and could) move to meet on a common
ground

I already have a list of defects and feature requests for C. That
would perfectly fit, there.

Jens
 
M

Malcolm McLean

בת×ריך ×™×•× ×©×œ×™×©×™, 28 ב×וגוסט 2012 08:27:55 UTC+1, מ×ת Jens Gustedt:
Am 28.08.2012 05:34, schrieb BGB:


That said, I don't really see the point of "void*" as it is in C++
nowadays. Since you'd have to do an explicit conversion any time you
use it, in C++ it has nothing that couldn't be done with a "unsigned
char*". In the contrary, "unsigned char*" allows byte based pointer
arithmetic and to inspect the contents on a byte base.
void * just documents that "this is an unsigned char * holding arbitrary
bytes". It's a bit pointless to require a cast to and from, because you
can't use the data in any way without converting it to something else,
but that's just a minor syntactical niggle.
 
J

Jens Gustedt

Am 28.08.2012 12:40, schrieb David Brown:
No, it is not defined nearly well enough.

There are ambiguities regarding padding, alignment, etc., especially as
the size of the fields change. Different underlying types of the fields
can make a difference (especially if you want something like an enum
type),

well C does only allow enums as implementation specific. For C the
only portable types are of signed and unsigned int and _Bool.

And the only real ambiguity that I see is for bit-fields that may
cross storage boundaries, here the standard allows either realize part
of the bitfield in one unit and the other part in the other or to
accumulate them in the second unit. As long as all bits fit, they must
be placed in the same unit, and if you want to force it to move to the
next unit you can place a :0.
No, these types are fine (I use them all the time). The issue is with
aliasing.

Sometimes in programming, you need to move data around in a format other
than the data's "natural" format. Maybe you've got data held in a
struct, and you want to make a copy of the whole struct. Or maybe you
want to pass data around using the Modbus protocol, which likes to treat
everything using 16-bit words. The trouble comes when you want to work
with a pointer to the struct data using a *uint16_t pointer (or any
other "incompatible" pointer). The compiler knows that the struct is
incompatible with a uint16_t, so the pointer cannot point to the struct,
and therefore nothing read or written to the struct can affect anything
read or written through the pointer.

wouldn't something like

struct toto _Alignas(uint16_t) t;

struct toto volatile _Alignas(uint16_t) vt = t;
uint16_t volatile*words = (uint16_t*)&vt;
/* do the fiddling that you want */
t = vt;

do the trick?

But perhaps I am too naive :)
Chars, or pointers to char, have the opposite problem - because they can
alias anything, the compiler has to assume that any access through a
char pointer can hit any other data (well, not /any/ other data - local
data in registers is safe). This can lead to lost optimisation
opportunities.

as long as the char* receives is value in the same spot modern
compilers should be able to avoid most problems, I would guess. But
yes if you have a char* that might point anywhere, it might impact any
of your variables. But such a pointer you usually receive as a
parameter of a function, so you should carefully use "restrict" to
tell the compiler what he may assume. (and oops we are back on track
for the the C - C++ incompabilities :)

I still not sure that I understand what you'd want. One generic data
type for each possible alignment specification?
So I would like to see types other than inefficient 8-bit types that
have the "alias everything" property, and 8-bit types that don't alias
anything.

You mean that any "char*" parameter to a function could ruin some
optimization? Never thought of it like that. But if you have a "char*"
(and not "char const*") then probably it is a good idea to add a
"restrict" where ever you may.
An alternative is to be able to specify aliasing more conveniently,
without having to use type-punning unions.

"uintXX_t*" would be good candidates, here.
Well, atomics helps a bit - but they are overkill. Mostly all you need
is to be able to say "make sure the store to variable X is completed
here", or "don't move the read before this point". You can get a fair
amount of this using volatile accesses, but sometimes it is much more
convenient with general barriers. Depending on the target cpu, you may
also want the barriers to issue additional instructions to flush buffers.

Declaring a variable that you'd want to flush as atomic would
certainly be overkill. But I don't see completely why

atomic_thread_fence

couldn't serve your purpose, at least aproximatively. True, it doesn't
ensure a fence for just one variable, but then again with aliasing
you'd have to make pretty much sure that your memory bus calmed down
before supposing that no other variable is affected by a load or
store. So my idea would be that atomic_thread_fence with a careful
selection of the memory_order that you pass as argument would come
relatively close to what you want.

Jens
 
J

James Kuyper

On 08/28/2012 04:21 AM, David Brown wrote:
....
Basically, I just want to be able to mix and match C and C++ code
freely. I want to be able to write in the same style in each language.
If I am not using C++ features, I want the code to be compilable as C
or C++, with the same functionality each time. And I want to be able to
do so without artificially restricting the features I can use in C.
It's okay if I have to #include a few standard headers, and put in a few
extern "C" wrappers.

Am I really asking too much?

Yes, you are. In essence, what you're really asking for is a single
language (we could call it "C/C++", converting a popular misconception
into reality :) ), with two modes it can be used in. What you're
actually faced with is two different languages, and it really doesn't
make sense for either one of them to be as tightly integrated with the
other as they would have to be to make what you want work.

If you can convince the C++ committee to port over a few more of C's new
features, then the C subset of C++ would provide virtually everything
you're asking for of C. That's a far more feasible goal to achieve than
integrating C and C++ together as tightly as you want.
 
J

Jens Gustedt

Am 28.08.2012 15:32, schrieb David Brown:
That's true - but I want to be able to unambiguously place fields across
boundaries, and I want to be able to use fields of different types in a
well-defined, portable and cross-platform way. For example, I want to
be able to write:

struct {
uint8_t a;
uint8_t b;
uint8_t c : 4;
uint8_t d : 4;
uint8_t e;
}

I want to be guaranteed that this struct takes 32 bits, and is aligned
to 8-bit boundaries.

In C this

struct {
unsigned a : 8;
unsigned b : 8;
unsigned c : 4;
unsigned d : 4;
unsigned e : 8;
}

should do what you want, provided CHAR_BITS is 8. Wouldn't that be
acceptable for you? I find it even clearer in its intent.

(Bitfields are guaranteed to have no padding and uint8_t is pretty
useless other than to guarantee that padding property or that its size
is 1.)
The atomic_thread_fence may be an answer here. I'll need to wait a bit
for support to make it into the tools I use, and then see what the code
is actually generated for it (and for the atomic types). But it is
always possible that I'll be able to strike this one from my wishlist.

What I have seen so far of the preliminary implementations in gcc and
clang it looked quite ok, there.

?

Jens
 
J

James Kuyper

On 08/28/2012 09:32 AM, David Brown wrote:
....
Ultimately, I'd like some way to tell the compiler what data, pointers
and/or types can alias what.

If you put two types in a union, within the scope of that union
declaration a conforming implementation must take aliasing between those
two types into consideration any time it's dealing with pointers that
could be pointing at different members of the same union object.

This won't help you, of course, if you want it to consider aliasing in
contexts where it's impossible that they refer to the same union object.
 

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
474,079
Messages
2,570,574
Members
47,207
Latest member
HelenaCani

Latest Threads

Top