← Back to context

Comment by leonidasrup

11 hours ago

C+ Standard Template Library is the best designed part of C++ library. It was designed by Alexander Stepanov.

https://en.wikipedia.org/wiki/Alexander_Stepanov

So, a few things (aside from the whole nomenclature argument already in another reply)

1. Stepanov's generic programming is a good idea. Every language you've seen with "generics" that's his idea, to the extent "The STL" is generic programming, everybody agreed it's a good idea.

2. But the STL is very old now, so while the idea is good, this is one of the oldest (Stepanov had tried this in other languages before C++) implementations and so other implementations are often better, because they've learned from experience

3. As well as pretty good generic algorithms, the STL also provides a lot of container types (what Rust would call collection types) and these vary not between "excellent" and "mediocre" but between "mediocre" and "inexplicably terrifying". The most charitable explanation is that they're just intended for teaching. If you teach DS&A to a Computer Science class you want the Extrusive Doubly Linked List to teach in class. If you write software you almost certainly never need this type, but it's front an centre in the C++ STL.

There's a single "I guess I would use this" container type, std::vector. It has an insane special case for bool, because WG21 are idiots, but it's otherwise a good enough growable array type and it's not worth building your own instead given the constraints.

Everything else is silly, or bad, or both. std::unordered_map feels like a hash table I made in class in the mid 1990s, but it's actually the provided standard hash table container in C++ 11 onwards. std::list is just that extrusive linked list for some insane reason. The Microsoft standard library maintainer STL could not offer me any justification for what std::deque is actually supposed to be for.

  • I would argue that even the basic concept behind STL is misguided. The rationale I often see is "you only need N algorithms for M container type, instead of N*M". This ignores the fact that algorithms and data structures are not independent of each other, and also that most of the time these days you're operating on vectors, so M ~= 1.

    Case in point: list::sort. You don't want to try running quicksort on a linked list. Or remove_if: great we've abstracted the difficult task of removing things without erasing them, except we can't do it on maps. (C++20 seems to add an erase_if, apparently admitting that the two-step remove/erase is silly).

    Then there's the fact that C++ iterators are basically pointers into the data structure, where for vectors (your common case) you'd do much better with index/container pairs, both for stability and bounds checking.

    • List::sort is present exactly for this reason

      STD::sort only works on a random access iterator, it won’t even compile if you try it on a list

  • AFAIK, std::map is also OK for what it is: an ordered, node-based (tree) map. These are (almost) always slower than hash tables. Of course, std::unordered_map, the std hash table, sucks because of unforced errors. For that, there is boost::unordered_flat_map.

  • > There's a single "I guess I would use this" container type, std::vector.

    About that one... I would claim that in a majority of cases where an std::vector is used, what the author really wanted was a similar type, but whose size and capacity are fixed on construction and never change. The standard C++ library does not offer such a type - so people use vector because it's handy.

    Agree with your takes on most of the containers. I also dislike how optionals are never used with containers as they were standardized later (and even then, problematically w.r.t. references). Thus, for example, if I lookup an object in a map of T's, the result should IMNSHO be an optional reference to a T.

    • > a similar type, but whose size and capacity are fixed on construction and never change.

      There is std::array for that. Also, for a type with fixed capacity but variable (up to that capacity) size, we're getting std::inplace_vector soon™.

      1 reply →

It has some very useful principles, but also some super-annoying gaffes and mis-design aspects. One example: Allocators. What a mess! Or the fact that if a map lookup fails, an exception is thrown. I can't count the times I've had some app just bail out on me with an at() exception, because the author neglected to handle it (and the map/unordered map interface did not force them to). That does not detract from Stepanov's important work.

  • The kind of programmer who don't check (or think through so that they can't fail) their map lookups is also the kind of programmer who don't bother with const. What a non-const unchecked map lookup gives you is a default-constructed value that has just been inserted for the only reason that operator[] returns a reference, which must "point" to something. That's bad and can be confusing, but it doesn't crash.

    I see that problem much more often than crashes due to unchecked map lookups in production, which are very rare for me. Less than once a year.

Nowadays also used by many of us (wrongly) to refer to the overall C++ standard library, instead of what was inherited from C.