1. 18
  1. 19

    There is a more general lesson: If you say exactly what you mean, the compiler and API have more of a chance of providing you with an efficient implementation. In the worst case, empty() is implemented as size() == 0 and will be inlined and so is as slow as explicitly calling size() == 0 (for something like std::vector, where size() is just a field read, that will also be inlined, so the generated code will be a load + branch-if-[not-]equal). In the best case, it can be more efficient than size() but it’s almost impossible to accidentally implement empty() in a way that’s slower than size() == 0.

    Tell your compiler what you mean and it can try to help.

    1. 3

      I think another lesson may be don’t use linked lists :) Containers that aren’t pointer-chasing can usually track their size easily.

      1. 1

        As long as all pointers but the head are immutable, you can just store the length at the head.

      2. 1

        To be fair, it is exactly what a person means. Thinking “empty, as in there’s zero elements” comes a lot more naturally than “empty, as in there are not one or more elements”

        1. 3

          Yeah, it’s less about what you mean than it’s about why you’re asking. Asking specifically if it’s empty is telling why you’re asking about the size, and can lead to a quicker path for that specific question. It’s asking “y’all sell apples?” instead of “how many apples do you have right now?”

          1. 1

            I think this is one of the strengths of the Python way of doing things. Even though it might be hell to debug, and terrible for performance, just allowing and catching an exception when something goes wrong lends itself to very clean code that doesn’t look much different from an if-else chain. Non-working example, just to vaguely gesture at the kind of thing I’m talking about:

            def foo(f):
              try:
                with open(f) as contents:
                  return contents.read()
              except:
                return f
            

            You didn’t even have to write a line like if f.is_openable():, because you tested what you actually cared about: that when you open and read f, no error occurs. That’s one layer of indirection removed. Bonus: there’s no risk of an attribute like ‘readability’ changing behind your back, because it’s checked in the same place it’s used! All the errors are encapsulated in and handled by the standard library functions open and type(f).read.

            Again, to reiterate: terrible for debugging & performance on actual hardware. If you’re using C++ in particular, you likely chose it for the performance, so this style makes no sense. However, maybe one day we’ll have a language that auto-refactors stuff like this into regular if-then-else chains, reducing the performance issue to a branch misprediction.

          2. 2

            That’s a matter of opinion. I don’t think of my kitchen bin being empty when it has 0 items in it.