Re: Transitioning libgomp from C to C++ implementation

Christopher Bazley via Gcc <[email protected]>
Newsgroups gmane.comp.gcc.devel
Organization Arm
Message-ID <[email protected]>
Hi Arsen,

On 08/05/2026 19:06, Arsen Arsenović wrote:
> Christopher Bazley <[email protected]> writes:
> 
>> I haven't worked on libgomp, but I agree with Martin's points.
>>
>> A lot of compilers can plausibly support modern C that will never
>> support modern C++.
> 
> libgomp is a GCC runtime library.  The only compiler of concern is GCC.
> 
>> Modern C offers features for convenience (e.g. auto) and macro-based
>> polymorphism (e.g. _Generic). Subtype polymorphism is more limited
>> than in C++ but still achievable with a reasonable degree of type
>> safety.
> 
> Yet, it offers no destructors, and no move semantics, which are arguably
> the most important features of C++11 and onwards.  I'd trade _Generic
> for those two, and make it out of the deal like a bandit.

OK. If that's what libgomp needs, then that's what makes sense. If what 
libgomp needed were ad hoc polymorphism, then _Generic would be 
perfectly adequate.

>> The issue with malloc seems to me like a canary in the coal mine. I
>> don't know whether libgomp makes significant use of callback functions
>> and event handlers, but they are likely to suffer from similar
>> issues. If the return type of malloc is perceived to be a problem,
>> then it is trivial to write a wrapper macro, e.g.
> 
> I don't see what issue that is.  Previously, allocation and
> initialization were separate.  They aren't in C++.  I don't see how that
> could apply to either callbacks or event handlers.

Callbacks and event handlers commonly have a parameter of type 'void *'. 
Requiring arguments of that type to be explicitly cast to some other 
pointer type makes source code harder to read and write, and actually 
detracts from type safety.

> (Note that every single instance of an allocation in libgomp falls under
>   the case of trivial initialization by merit of being a C codebase, and
>   ergo, even the initially-proposed mechanical conversion of allocation
>   sites is not incorrect, just presumably inconsistent with newer code)
> 
>> C projects 'upgraded' to C++ can end up worse than reasonable code
>> written from scratch according to good idioms of either language, due
>> to:
> 
> Yes, this isn't surprising.  That's exactly why I was arguing not to do
> mechanical explicit void* casts in the cases where memcpy is immediately
> followed by initialization.
> 
> The instances where allocating uninitialized memory is genuinely useful
> are in the tiny minority, but they do exist in libgomp.  These need to
> still call malloc explicitly with a computed size, as in C.
> 
> There are also a number of useful flexible array members, but for these,
> each such type can provide an allocation-and-construction helper.
> 
>> - C++'s overly strict type rules concerning void *. Without care, C++'s type
>>    system has the ironic effect of making code *less* type-safe. (For the same
>>    reason that it would be less type-safe for MyPy to require explicit
>>   conversion of 'Any' to some other type.)
> 
> ?  Those rules aren't overly strict.  I don't follow how this analogy
> with 'Any' demonstrates a lowering of type safety.

Casting discards type information. Preventing implicit conversion of 
type 'void *' to other pointer types on assignment or initialisation 
prevents the compiler from checking that qualifiers weren't discarded, 
or even that the expression whose type is being cast actually has a 
pointer type.

For example:

void callback(const void *p)
{
   int *q = (int *)p; // whoops!
   *q = 1;
}

void callback(long int p)
{
   int *q = (int *)p; // whoops!
   *q = 1;
}

I believe that the concept of any-type (as expressed via void *) is 
orthogonal to the concept of qualified type or subtype. Python's 
treatment of the type annotation Any supports this point of view.

This flaw in the design of C++ is also what necessitated the invention 
of nullptr, which would otherwise be unnecessary. Had the purpose of 
'void *' not been misunderstood, nullptr could simply have been defined 
as a macro: ((void *)0).

>> - C++ constructors being unable to return a failure indication, and the
>>    gymnastics required to work around that without using exceptions (leading to
>>   many identically-named construction helpers).
> 
> I've been writing C++ in places where exceptions would be inconvenient
> for many years now, and this is a fairly infrequent requirement IME.
> 
> It will be fine.

Sorry but I'm not sure what you mean here.

Are you saying that wanting to return a failure indication is 
infrequent, that doing without exceptions is infrequent, or both? I 
guess it depends what kind of code you are writing and what kind of 
system it runs on.

Nowadays a lot of programmers would claim that malloc cannot fail, or at 
least that programs have no need to recover gracefully if it does fail. 
That does not apply to interactive programs running on operating systems 
that do not overcommit memory, but it might apply to non-interactive 
programs running on such systems.

>> - Erasure of designated initialisers that were vital for keeping the code
>>    readable and maintainable (by preventing definitions, for example array
>>   initialisers and enumerations, from diverging).
> 
> Designated initializers exist in C++.

Not usefully, as far as I can tell: https://godbolt.org/z/7j6fdqzrb
I find it frustrating that C++ lags more than a quarter of a century 
behind C in this respect. (I am aware why C++ does not allow arbitrary 
order of initialisers, but I do not see that as a point in its favour: 
debugging a mess caused by misordered  constructor and destructor 
invocations is not fun.)

>> - Gratuitous loss of the ability to find method definitions and usages using
>>    simple tools like 'grep'. (I do not mean gratuitous as a perjorative but in
>>    the specific sense that some useful patterns require subtype polymorphism, in
>>    which case the equivalent C code would have the same issue, but most code
>>   does not.)
> 
> There's no C pattern that is impossible in C++.  There's certainly
> nothing that requires dynamic dispatch that wouldn't require a
> (hand-rolled) vtable in C.

The issue is not impossibility but reduced maintainability of code that 
has fewer unique identifiers. It's possible to similarly reduce the 
maintainability of C programs by using structs to implement namespaces, 
and invoking functions via members. Most C programmers consider that the 
downsides outweigh any advantages.

> Do note, however, that such a table exists in libgomp already:
> https://gcc.gnu.org/cgit/gcc/tree/libgomp/libgomp.h#n1391
> 
>> As I said, I'm not familiar with libgomp, so I have no idea how much
>> it might benefit in areas where C++ truly does excel, such as object
>> lifetime management. In my experience, few C projects have complex
>> object lifetimes (by design), but maybe libgomp is one of them.
> 
> Trivial objects still benefit from destructors.  unique_ptr is a
> distilled example.
> 
>> Lastly, some C programmers are put off contributing to C++
>> codebases. I don't know how common the converse is.
> 
> Given that gcc is already in C++, it seems that that bridge was burned.

It isn't as black and white as that. I know of at least one programmer 
who contributes to GCC because GCC is still largely written in the style 
of a C program, but does not feel inclined or able to contribute to 
Clang. He is probably unusual in that he is outspoken in his opinions.

> Have a lovely day.

You too.
-- 
Christopher Bazley
Staff Software Engineer, GNU Tools Team.
Arm Ltd, 110 Fulbourn Road, Cambridge, CB1 9NJ, UK.
http://www.arm.com/
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.