It's quite common to want to write a header for a library written in C
that can be used from both C and C++. Conceptually, this is quite
straightforward, but in practice it usually seems to get needlessly
messy. I think it would be easy to make a very few minor and safe
changes to C++ that would make this process a lot cleaner.
Let's take a very simple example. I have a function that acts as
strncpy except that it returns true if it copied up to a null
character on the source string, and false if it stopped because the
the destination buffer was false. This might have the signature:
bool my_strncpy( char const* src, char* dst, size_t n );
Let's look at what's needed to turn this into a reasonable quality C/C
+
+ header. It starts off easily enough:
1) Include guards. Easy enough: the same syntax works in both
languages.
2) Include a header for size_t. Again, easy enough: the <stddef.h>
header does this in both languages, and avoids any complications that
might (in theory if not in practice) exist with <cstddef> putting it
in the std namespace.
But now things start getting messy:
3) In C, we need a header for bool. In principle, this is easy:
<stdbool.h> does the right thing in C and is (nearly) a no-op in C++.
But in practice, it's bad practice to include <stdbool.h> from a
library header in C. It's not uncommon to find programs originally
written for C90 that have defined true, false and (more troublesomely)
bool as integers. The macros in <stdbool.h> would break that. The
boolean keyword in C99 is spelt _Bool, and it's good practice to use
this instead of the bool macro in a C header. We're not allowed to
define _Bool as a macro in C++ as it's a reserve word, so we're left
either doing something like:
#if __cplusplus
#define MYLIBRARY_BOOL bool
#else
#define MYLIBRARY_BOOL _Bool
#endif
4) In C, the restrict keyword (might) allow the compiler to make
better more assumptions about the two arguments and therefore optimise
it better; it also potentially allows for a compiler warning if the
same argument is supplied for both parameters. So far as I'm
concerned, the jury's still out as to whether this really does make a
significant improvement, but it's reasonable to assume a library
author might want to add it to the function. This means adding
another MYLIBRARY_RESTRICT macro as restrict is not permitted in C++.
5) We need to wrap the declaration in an extern "C" block that's only
compiled in C++, or alternatively put extern "C" before each
declaration in the header.
6) Finally, we may need some platform-specific code to make shared
libraries work, for example, on Window's we're likely to want to place
__declspec(dllimport) before each declaration in the header. This
needs wrapping in a macros, say MYLIBRARY_API, so that it's only used
in the appropriate places.
I've just implemented all of that for my single function library and
my header is now has 32 non-empty lines, of which 27 are preprocessing
directives. In a real library, I'd have rather more declarations and
so the content-to-noise ratio improves a bit; I may also choose to
split the MYLIBRARY_BOOL, RESTRICT and API macros into their own
"mylibrary/config.h" so they can be reused. But none of that changes
the fact that these hoops need jumping through. Nor does it change
the fact that my declaration is now covered in macros:
MYLIBRARY_API MYLIBRARY_BOOL
my_strncpy( char const* MYLIBRARY_RESTRICT src,
char* MYLIBRARY_RESTRICT dst, size_t n );
In my experience, some (many, even) C library authors don't especially
care about C++. Sometimes, avoiding naming their function parameters
'new' or 'class' is about as far as they're willing to go in the name
of C / C++ compatibility. If they happen to need a MYLIBRARY_BOOL
macro to ensure C90 compatibility, they'll probably use it to ensure C
+
+ compatibility too, but if (when) C90 compatibility is not relevant,
they'll just stick to _Bool as, let's face it, those macros hardly
improve the code's readability.
I think that with three minor additions to the C++ language, we could
significantly improve C / C++ compatibility and make these things a
lot easier. (Additions A & B are the important ones.)
A) Make _Bool an alternative token for bool. So far as I can see,
this is as simple as adding it to the table of alternative tokens in
2.6 [lex.digraph], and to the table of alternative representations in
2.12 [lex.key]. I don't think any actual text is required. As a
(less good) alternative, the C++ standard good mandate that _Bool is
defined as a macro in <stddef.h> -- that header on the basis that it
is the lightest-weight and most included header there is. (Although
<stdbool.h> might seem a better choice, it isn't. The situation when
_Bool is used is precisely the situation in which <stdbool.h> is not
included. Having it additionally defined in <stdbool.h> might be
acceptable, though.)
B) Introduce restrict as a C++ keyword, but with no meaning. I think
most people would accept that using 'restrict' as an identifier in a C
+
+ program is now bad practice. Presumably when the C standards
committee were drafting C99 they did research and came to the
conclusion that it was only rarely used as an identifier in existing
code, else they would have named the keyword something else (e.g.
_Restrict). It seems fair to assume the same is true in C++. In
addition to listing it in the table of keywords in 2.12 [lex.key],
some text would need to be written for 7.1.6.1 [dcl.type.cv]. This
could be as simple as editing the first paragraph to say that restrict
was always redundant (are therefore ignored), and adding a non-
normative footnote saying it is intended to be used for compatibility
with C.
C) Add a non-normative note paragraph to 7.5 [dcl.link] encouraging
vendors that support other linkage-like options, such as
__declspec(dllimport), to extend the compound linkage specification
grammar to allow them on the extern "Lang" blocks, e.g. extern
__declspec(dllimport) "C" { ... }. There's precedent for giving
guidance such as this in the standard, for example in
[lib.iterator.traits] guidance on __far pointers is given.
Putting these three minor changes together means that a C/C++ library
header can now contain clean declarations such as:
_Bool my_strncpy( char const* restrict src, char* restrict dst,
size_t n );
That's far more readable than the earlier version with lots of macros
scattered about. As a result, it ought to result in C libraries
remaining compatible with C++ as their authors gradually adopt C99
features such as _Bool and restrict are gradually adopted.
Any thoughts? The changes are all minimal and unlikely to break
anything. It's too late now for C++0x, sadly, but they certainly
seem worth considering for the next version (or a TR if there's an
appropriate one).
--
Richard Smith
--
[ comp.std.c++ is moderated. To submit articles, try posting with your ]
[ newsreader. If that fails, use mailto:std-cpp...@vandevoorde.com ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
> 4) In C, the restrict keyword (might) allow the compiler to make
> better more assumptions about the two arguments and therefore optimise
> it better; it also potentially allows for a compiler warning if the
> same argument is supplied for both parameters. So far as I'm
> concerned, the jury's still out as to whether this really does make a
> significant improvement
It is? restrict allows parallelization where it otherwise would not
be possible. This is steadily becoming more important as clock rates
stagnate and core counts increase.
We really need something like restrict in C++. The semantics of C's
restrict are a bit funky so some tweaking should be done but we
absolutely do want to express the kinds of (non-)aliasing relationships
restrict provides.
-Dave
Brilliant idea.
I would be very much in favor of this alone, with or without the rest.
This would be incredibly useful. I can leave out all of those
obnoxious declspec macros of each function definition, and just have a
couple of lines at the top and bottom of the file to deal with the
windows-isms of shared objects. This is has great value even for pure C
++ code apart from any C-C++ interoperability.
I suspect this is just another thing relegated to this nebulous
"module" support, but this would provide great benefit /now/. Hell,
this should be sent to Microsoft directly for inclusion in their next
compiler. Anyone from Microsoft reading this? Please also include
stuff like calling conventions like thiscall and __cdecl too in the
extern "C" block declaration structure. Would make writing DLL headers
so much more sane.
> > So far as I'm concerned, the jury's still out as to whether this
> > [restrict] really does make a significant improvement
>
> It is? restrict allows parallelization where it otherwise would not
> be possible. This is steadily becoming more important as clock rates
> stagnate and core counts increase.
OK. Perhaps it would be more accurate to say in the sort of C
applications I write, on the operating systems and architectures I'm
familiar with, using the compilers I've bothered investigating, it
doesn't appear to do anything much. But it's a long time since I've
been a C programmer (rather than a C++ programmer who occasionally
writes C) and I perhaps don't have the breadth of recent experience to
comment sensibly. In any case, something along the lines of restrict
does seem like it might have the potential to help compilers optimise
code. And I do tend to sprinkle 'restrict' around my function
declarations in C, even though I'm not certain it's much of a benefit.
By the time we're thinking about C++1x (or 2x, or whatever the version
after C++0x is), I expect we'll know whether restrict has been a
success in C, and if not (or only partially), how it can be improved.
But the idea of adding 'restrict' as a meaningless keyword that can be
placed wherever cv-quals are currently permitted doesn't require us to
think about that, but it's still useful for C / C++ compatibility.
Perhaps instead of saying that restrict has no effect, it could be
left implementation defined with a non-normative note saying that the
intention is for it to be used in a manner compatible with C. That
would encourage C++ compiler vendors to implement restrict in C++,
which means that if/when the C++1x/2x standard comes to look at
standardising its semantics, they'll actually have experience of how
it might interact with C++'s features which would hopefully reduce the
chance of standardising something suboptimal.
Richard Smith