1. 12
  1. 12

    I agree with almost all of these, but not std::array. It has two advantages over raw C arrays. First, it is bounds checked (related, use .at() instead of []) and the bounds are part of the type system and so you can get hard compile failures if you pass the wrong sized array as a function argument. Second, it has a .size(), which is very useful for code review of interop with C: any C function that takes a C array should be passed the result of calling .data() and .size() on some C++ type. If it is passed anything else, think very hard about security in code review. If it is passed both of those, nod and move on.

    1. 6

      Yes, and std::array is a value type, whereas C arrays are basically raw pointers except in some cases that I honestly don’t remember the rules of. So, as you pointed out, they’re nearly useless as function parameters. Thus the C idiom of a struct wrapped around an array just to give it value semantics and size-checking.

      std::array is much better. I do not understand the examples given of why it’s bad; what’s wrong with them?

      1. 7

        Unrelated to this topic but you two are the reason I submit most of the c++ tagged stuff, next to liking it myself and working professionally with it. Comments are often so insightful and I learn from them. Thanks both David and Jens.

        1. 0

          This is a great discussion, I just wish we weren’t promoting a registered sex offender’s content to get that discussion going.

          1. 1

            It this a case of “once guilty, always guilty?” I see he was convicted and served his time 11 years ago. Is there no possible redemption?

        2. 2

          The annoying thing with std::array is that it isn’t an array. This means that all of the nice initialiser syntax borrowed from C does not work. For example, you can’t write initialisation with explicit source indexes. I suppose that would be fixable (ish) by adding a constructor to std::array that took an initialiser list of size_t and T pairs.

      2. 9

        Prefer char over std::byte

        Nope. Char is problematic. It’s usually signed, which is a bad choice for many byte operations; you don’t even know whether it’s signed because that’s a compiler option, making it fertile ground for compatibility bugs; and it’s not even guaranteed by the language spec to be a single byte!

        std::byte is simply defined as enum class byte : uint8_t { } … then, yeah, it’s got a bunch of associated operator overloads to allow bitwise operations because C++ has no convenient way to do that. But what is someone who objects to including a (verbose) library header doing using C++ in the first place?

        Most of the other advice I agree with, though. Except the array one.

        1. 4

          Reminds me how in C compilers for PDP-10, char was 9 bits — 1/4 of the 36-bit word. I’ve only seen it in an emulator, but it completely changed my upper bound for possible compatibility issues. :)

        2. 4

          I was with the article until the std::tuple part.

          But there is no such reason to prefer std::tuple over struct

          There certainly is! Tuples, unlike one-off structs, can be represented with template varargs, and generic code over them be produced via said templating. I’m talking implementations of std::hash, operator==, and more. In a project at $WORK recently, I had to create a whole hierarchy of classes which needed to contain different data, but all implement multiple standard functions that depended on the data. Tuples saved me hundreds of lines of boiler plate template specialization.

          1. 1

            I think the key place to use std::tuple is for templates. Some of the examples are using them for multiple return values. Here, using a struct is better because it explicitly documents, in code, what each return value is, and still works with structured binding. If you want to be able to apply something to every argument in a structure then std::get over a tuple is far more convenient in templates.

            Most of my uses of std::tuple are actually erased at compile time. With std::apply and std::forward_as_tuple, you can easily write wrapper templates that apply some transform to every argument in a function call.