C constructs that still don't work in C++

(lospino.so)

25 points | by jalospinoso 3 days ago

9 comments

  • mjs01 1 hour ago
    Some unmentioned incompatibilities I've encountered that makes a C header not directly usable in C++:

    - C `_Atomic(T)` and C++ `std::atomic<T>`. C++23 has C compatible header `stdatomic.h` that defines `_Atomic(T)`, but it's still problematic

    - C `_Noreturn/noreturn` and C++ `[[noreturn]]`. C23 `[[noreturn]]` makes them compatible

    - C inline and C++ inline are different. Good news is their `static inline` are the same

    - C has anonymous struct. C++ doesn't. Both have anonymous union though

  • mike_hock 25 minutes ago
    The thing with the flexible trailing array member is a C++ design flaw. Now the fix wouldn't be to allow those "flexible arrays" in C++, at least not the way C has them, but it should have a concept (not that kind of concept) of types that are indeterminately sized at compile time and whose size is determined at construction.

    If you're allocating something on the heap anyway, you shouldn't be forced to pay for an indirection in order to have some variable-sized data in the object, you should just be able to put it all in the one allocation. (Sure, you can achieve that with placement new hackery but that certainly isn't "idiomatic" C++.)

    Of course that's completely incompatible with the way allocation and construction work (storage has to be allocated before the constructor runs). Hence "design flaw" rather than "missing feature."

  • jalospinoso 3 days ago
    I wrote this after repeatedly seeing experienced C programmers hit the same sharp edges while moving into modern C++ codebases.

    Many of these differences are intentional and defensible from the C++ side. But some are still surprising because they invalidate patterns that were historically common, performant, or idiomatic in C.

    The interesting part to me isn’t "C vs C++," but where the languages diverged philosophically: object lifetime vs raw storage, stronger type systems, implicit conversions, ABI and optimization assumptions, and the boundary between "portable" and "works on my compiler."

    I’d also be curious which C constructs people still genuinely miss in modern C++. For me, restrict is still near the top of the list.

    • pjmlp 59 minutes ago
      I appreciate that restrict isn't there, because it is yet another UB source, programmer knows not to do errors kind of attitude, and secondly no one seems to care enough to write a language proposal for it.
    • hgs3 1 hour ago
      Not sure if you're aware, but defer is proposed for C2Y [1]. It's already available in Clang behind a compiler flag. It is interesting how the languages continue to diverge.

      [1] https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3734.pdf

      • pjmlp 58 minutes ago
        Because the communities aren't the same.

        C++ is 1990's Typescript for C++, while C folks still think is a portable Assembly instead of designed to an abstract machine model.

        As such C++ community embraces high level abstractions and type systems improvements, whereas C wants to still code as targeting classical hardware.

        • jstimpfle 4 minutes ago
          Caring for the actual assembler output in selected critical pieces of code is not the same as ignoring the abstract machine model. What you claim is simply not the case if you check actual proficient systems programmers. Of which there are an astonishingly high share C and C++-but-mostly-C programmers.
  • p0w3n3d 24 minutes ago

      Address white_house{
        .street = "1600 Pennsylvania Avenue NW",
        .city = "Washington",
        .state = "District of Columbia",
        .zip = 20500,
      };
    
    For me this is the most important initialization in C that helps with clarity so much, I used mostly structs to have function parameters intialized like this

    However C++ had at time no default initialization for unmentioned fields, so in 2017 I had to remove it when converting the code to C++

  • AdieuToLogic 1 hour ago
    From the article:

      In 2019 I wrote a short survey of C constructs that do not 
      work in C++. The point was not that C is sloppy or that C++ 
      is superior. The point was that C++ is not a superset of C, 
      and that C programmers crossing the border should know 
      where the checkpoints are.
    
    C++ was a superset of C 30-ish years ago. Now, as the author correctly identifies, it is not as both have taken different evolutionary paths.
    • electroly 15 minutes ago
      30 years ago, in C89 and pre-standard C++, it was the case that `int foo()` in C is a function that accepts any parameters, and in C++ it is a function with no parameters. In C89 you have to write `int foo(void)` if you want no parameters. This counterexample to C++ being a superset of C was well-known even back then.

      Another well-known counterexample is implicit conversion from void*. In C89 you can do `int* foo = malloc(100);` but in C++ it requires an explicit cast from void* to int*.

      I don't believe there was ever a time, even pre-standardization, when C++ was a strict superset of C; it always had little incompatibilities here and there.

    • pjmlp 1 hour ago
      Already in C++98 there were differences.

      ?: has another execution priority.

      Implicit cast scenarios are reduced in C++.

  • grougnax 18 minutes ago
    Give up C++ and use Rust
  • grougnax 18 minutes ago
    At this point, just give up c++ and use Rust
  • dhruv3006 58 minutes ago
    > restrict: a C promise, not a C++ contract

    This takes the cake.