Hacker News new | ask | show | jobs
C++20 Reference Card (bfilipek.com)
116 points by cxcl 2327 days ago
6 comments

Thanks. I really don't want another newsletter in my email inbox!
[author here] you can always unsubscribe at any time, I think it's a fair trade. So please consider removing that comment with the links.
Whenever I see new C++ features I'm all the more impressed with how Zig manages to provide capabilities similar to -- and at least as powerful as -- type templates, value templates, constexprs, concepts, and conditional compilation (preprocessor), with a single feature and keyword, without any type/template-level programming, a preprocessor or AST macros (i.e. mini-languages different from the ordinary computation language that one has to learn and can make code quite opaque), all in a language that is about as simple as C and can be fully learned in a day or two.
> capabilities similar to -- and at least as powerful as -- type templates, value templates, constexprs, concepts, and conditional compilation (preprocessor), with a single feature and keyword

are you sure ? zig does not seem to have something that looks like concepts (an issue is open : https://github.com/ziglang/zig/issues/1268) nor a way to put restrictions on a given type passed to a function or series of function.

e.g. it seems that what in C++ would be (with concepts, but the same can be written more verbosely with C++03 templates if needed)

    void f(IsFoobar auto x) {
     
    }
    void g(IsFoobar auto y) {
     
    }
would require in zig to have a comptime test for the "foobar" nature of T in every function, which looks like a downgrade and does not expose the requirements cleanly in the function interface.
Here's the C++ example from the article:

    template <class T>
    concept SignedIntegral = std::is_integral_v<T> &&
                            std::is_signed_v<T>;
    template <SignedIntegral T> // no SFINAE here!
    void signedIntsOnly(T val) { }
Here's the equivalent Zig code. You can run this with `zig test example.zig`.

    const std = @import("std");
    const assert = std.debug.assert;

    fn signedIntsOnly(val: var) void {
        comptime assert(std.meta.trait.isSignedInt(@TypeOf(val)));
    }

    test "concepts example" {
        var x: u32 = 1234;
        signedIntsOnly(x);
    }
This produces the compile error:

    ../lib/std/debug.zig:221:14: error: unable to evaluate constant expression
        if (!ok) unreachable; // assertion failure
                 ^
    ./example.zig:5:20: note: called from here
        comptime assert(std.meta.trait.isSignedInt(@TypeOf(val)));
                       ^
    ./example.zig:10:19: note: called from here
        signedIntsOnly(x);
                      ^
    ./example.zig:8:26: note: called from here
    test "concepts example" {
                            ^
    
It accomplishes the same thing, just a bit more verbose, since it is using the general tools of the language rather than special syntax.
> assert(std.meta.trait.isSignedInt(@TypeOf(val)));

well yes, that's my point. You could also write

    void signedIntsOnly(auto val) {
        static_assert(std::is_integral_v<T> && std::is_signed_v<T>); 
    }
in C++ but it's quite preferable to refactor this inside the types themselves (for the same reason that you want to refactor any other kind of code in general - besides you don't want to go read the implementation of your functions every time do you ? IDEs show us only prototypes for a reason).

How would you for instance specialize a generic function in Zig ? Does zig accept

    fn complexmathoperation(val: var) void {
        if(isInt(@TypeOf(val))) { 
        }
    }
and in another module

    fn complexmathoperation(val: var) void {
        if(isSomeKindOfLibrarySpecificMatrix(@TypeOf(val))) { 
        }
    }
and properly dispatches between both ?

I'd be hard pressed to say that it is "as powerful" given what I'm seeing for now.

> besides you don't want to go read the implementation of your functions every time do you ? IDEs show us only prototypes for a reason

The IDE can be changed to show you assertions in addition to the signature, just as it also shows doc comments. That the signature is special isn't some law of nature, it's just habit (and users of untyped languages don't even have those).

> How would you for instance specialize a generic function in Zig ? ... and properly dispatches between both ?

No, you'd need a single definition with a comptime branch.

> I'd be hard pressed to say that it is "as powerful" given what I'm seeing for now.

What you're seeing is personal taste. In my taste, that Zig doesn't allow overloading and has clear dispatch is clearer is more preferable (in fact, it's touted as one of Zig's biggest strengths); in yours, it is less preferable. That's perfectly OK, but it's got nothing to do with expressive power (which has a pretty good and precise definition: https://youtu.be/43XaZEn2aLc). There is a reason why some languages are more complicated while others are simple -- some people aesthetically prefer the more complex languages while others prefer simpler ones. What is impressive about Zig is that despite being simple, it's not giving up power, at least in those aspects I mentioned. C, another simple language, cannot do those things.

> zig does not seem to have something that looks like concepts ... nor a way to put restrictions on a given type passed to a function or series of function.

Of course it does. Just place a comptime assertion on the @TypeOf a var.

> would require in zig to have a comptime test for the "foobar" nature of T in every function, which looks like a downgrade and does not expose the requirements cleanly in the function interface.

Cleanliness is a matter of personal aesthetic preference, and a rich type specification "in every function" is not less work or less clear than a comptime assertion in every function. In either case you get a clear compilation error. Requirements are never fully specified in the function's interface unless you have dependent types or contracts (both with their own problems and tradeoffs). Even Haskell has partial subroutines. What portions of the spec that is already enforced at compile type should be exposed in the signature -- keeping in mind that it will never be all of them -- is a matter of taste. Zig might shift things around a bit, but the capability to enforce a specification that is at least as strong as structural types at compile-time is already there.

The best part is that Zig accomplishes that without adding a ton of reserved words and the syntax to accompany them, a la C++
Then again, it lacks the eco-system, which is what makes me reach out to C++ when going outside Java/.NET.
Sure. Zig isn't even production-ready yet. That doesn't mean that its ability to do so much with such a minimal design isn't very impressive.
Agreed, looking into other eco-systems is always a learning experience.
Could you give or point to some examples?
Damn it looks good. I wish it could target and consume .NET or Java.
What does that even mean?

"Target" I guess could mean that it could compile to code for those virtual machines? That would make some sense although for a language that sets out to compete with/replace C it would be a bit strange.

For "consume" I don't know ... do you mean "call into", or something?

Not strange at all, apparently many still aren't aware that C++/CLI exists.

CLR was designed from the start to support C++.

Back in the 2000, "Everything.NET" Microsoft, there was the goal of having everything on top of .NET, it just did not play out that way due to the endless Windows/Office vs .NET politics across MS teams.

Using C++/CLI is much more productive for interop than manually writing P/Invoke declarations.

Or using C++/WinRT to wrap C++ API into COM/UWP libraries.

Likewise on Java, microEJ and Android world, with Android Studio, Eclipse, Netbeans and Oracle Studio you can easily do mixed language development and debugging with C and C++.

Any systems programming language that wants a place at the table on these platforms, needs to cover these workflows as well as C++ does.

Correct on both cases.

With .NET I'd love integration with existing types and generics too.

Actually, I was just thinking aloud. I don't really mean for Zig authors to target .NET, but rather have .NET Core runtime and C# to have the concept of compile-time values and types.

JIT compilers already do something like that automatically.
Looks interesting. I wonder how it compares with Rust and Kotlin.
> Range-based for with initialiser

Gee, I would really love something like Python's enumerate() and zip() (but yes, I know that there's alternatives available that are not in the standard library).

> std::format / Python-like formatting library in the Standard Library!

Yes, this is a good addition.

> Ranges / A radical change in how we work with collections!

... but I still have to use verbose warts like std::iota in order to make a collection of integers?

EDIT: no, apparently not. Sanity prevails! (see [1]):

    for (int i : std::views::iota{1, 10})
        std::cout << i << ' ';

[1] https://en.cppreference.com/w/cpp/ranges/iota_view
Do people like printed reference cards?

I’m working on a Swift Cookbook.

It started as web pages but I’ve moved it to Github. Considering doing an ePub or trying to cram a lot into a one pager.

Having Strings, Sets, Arrays, Dictionaries, and Functional examples on one page might be useful, for example.

Here are my functional examples:

https://github.com/melling/SwiftCookBook/blob/master/functio...

If you know a couple languages, my thinking is that a few pages of examples should be enough to get started in another language.

Not sure about printed per se, but would definitely appreciate a modern concise Swift reference!
Could someone please explain why you'd put nodiscard on a constructor? I'm a bit confused as to when that's needed.
To avoid this insidious bug:

    std::lock_guard<std::mutex>(m);  // guard is immediately thrown away
versus the correct

    std::lock_guard<std::mutex> g(m);  // hold mutex til end of scope
Note for people who don’t see the difference: if a guard doesn’t have a name, it won’t survive until the end of the scope, thus doesn’t guard anything.
I might be wrong here, but I think that the first one is a declaration of a guard with the name m using the default constructor.

The same way that int (a); and int a; are equivalent.

So the guard should remain until end of scope, but won't guard anything.

Lock guard has no default constructor, so the line will in fact materialize a temporary lock guard around the mutex m, then throw it away at the semi-colon, thus locking nothing.
lock_guard doesn’t have a default ctor, you have to pass a mutex (or presumably invoke a move ctor, but I can’t see one on cppref).
If it's constructed as a temporary rvalue but never stored or passed as an argument, I think there's places where the compiler can elide the code entirely. Whereas you might prefer it have the system side effects associated with construction and destruction.
So it's for copy/move-constructors only?
No. Sorry for being unclear. [1] is a great video that covers a lot of good stuff in particular this usability bug. It's worth watching in its entirety.

If you don't want to sit through the video, here's the broken code in question:

    void Obj::update() noexcept {
        unique_lock<mutex>(m_mutex);
        do_the_mutation();
    }
"unique_lock<mutex>(m_mutex);" has no effect, but you might mistakenly think that this will block on m_mutex. Labeling unique_lock's constructor as [[nodiscard]] would force you to think twice. See [2] for more details.

[1] https://youtu.be/lkgszkPnV8g?t=1767

[2] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p177...

Ahh, that makes sense! On my phone at the moment but will check out the video too, thanks!
After not using C++ for 15+ years , looking at it now makes my eyes bleed.
Using modern C++ everyday, that’s how I feel looking at C++98 code ;-)
I use it everyday, and I'm still very eager to stop using it due to its growing complexity.

I'm trying to gradually replace C++ with Rust as my primary language, but it's still a lot easier to get a well-paying job in C++ than in Rust.

Rust is both more complex and uglier than modern C++. If your complaint is having to learn new stuff, then Rust isn't the solution.
i’m not disagreeing with you (i’m learning rust, so don’t have a strong opinion yet) but am curious how you feel that way?
That's how I felt looking at C++17 deduction guides, and that's even though I've actually been using C++. If you haven't seen them yet you should check them out ;)
All this stuff actually made it in!? Concepts look great! Love ranges too.
There is nothing that cannot make it in C++.