When I first started the update process for the Lucena Utility Library (LUL), there were no good equivalents that I was aware of in the open source world. A short time after I started blogging about it, Google began raising the profile of Abseil, its C++ augmentation library. Pieces of Abseil deal with feature detection and compiler abstraction, pieces of it provide abstractions and back-ports of Standard Library features, and pieces are centered around utility code and replacements for various pieces of Standard Library functionality. So how does it compare to LUL? In this part, we’ll start by breaking down Abseil’s architecture and motivation.
Herding cats
At a high level, Google appears to be seeking commonality across its C++ code bases. This is different from homogeneity, as it’s more about encoding certain best practices and easing code interoperability than enforcing a single way of doing things. One way of achieving this is by encouraging code users to “live at head”. This little mantra encapsulates a philosophy of obviating the need for special allowances for version mismatch issues, meaning a developer can just pull from a repository’s master branch and go, perhaps after running an automated migration tool if there has been some refactoring or other otherwise-breaking changes. One benefit of doing things this way is that system-wide changes can (theoretically) be rolled out without mass coordination. We won’t be discussing the tradeoffs and merits of such a system in this post; instead, we’ll consider some of the ways it manifests in the design of the Abseil C++ libraries themselves. Note that there are other official reasons to adopt Abseil, but I’d argue that this is the one that drives Google themselves.
Abseil is the manifestation of a philosophy, the foundational core ensuring that certain ideas are reinforced throughout all higher-level code. As such, it brooks no other masters; nothing chooses to override Abseil that Abseil itself does not allow. This means that if you restrict yourself to using Abseil’s abstractions (e.g., for certain Standard Library features), you’ll be the beneficiary of its guidance, using whatever implementation is most appropriate to your compile-time environment. Relatedly, Abseil has its own feature detection capabilities for compilers, hardware platforms, and the Standard Library itself. This provides a basis for code built on Abseil for build-time adaptation.
There is pressure on the design, though. As that omnipresent basis upon which everything else is built, there is an incentive to incorporate all the core utilities the “average” library consumer might need in an effort to prevent divergence from the guiding philosophy. Being too restrictive about what is Abseil-worthy creates the potential for users to rely on conflicting ways to achieve basic objectives. This is where the balancing act starts.
Warts are in the eye of the beholder
For starters, Abseil doesn’t just provide occasional wrappers for the C++ Standard Library; it sometimes seeks to supplant it. For example, the Abseil hash tables are intended to actually replace std::unordered_map
and std::unordered_set
. These new containers have a number of very positive qualities and one usability oddity: std::hash
and friends are replaced with absl::hash
and friends as hashing algorithm defaults. Unlike std::hash
, which has the unusual characteristic of being a std
template that you specialize as a customization point, absl::hash
is intrusive—you must modify your types to make them hashable. If dealing with this is a pain point, you can simply forgo using Abseil’s hash tables or absl::hash
, as there is no knock-on effect with regard to the rest of the library.
In contrast, we have absl::Mutex
and friends, which replace some fundamental chunks of the Standard Library’s multithreading code. While the hash table functions are designed as drop-in replacements for their std
equivalents, absl::Mutex
and the related Abseil synchronization library are semantically and functionally just a little bit different. One side effect of this is that it is extremely difficult to switch away from Abseil synchronization once it’s taken root. Frustratingly, the core changes seem to be the result of designer preferences, and much like Google’s infamous banning of C++ exceptions, a lot of the ex post justification comes down to inertia. Another consideration is that concurrency and parallelism are significant focuses in the rapidly evolving C++ Standard Library; not only are issues being systematically addressed, but significant new features are being built out. Replacing a fundamental primitive means either forgoing new functionality or having to maintain an entire parallel ecosystem for parity—Hello, boost!
Finally, Abseil attempts to maintain backward compatibility all the way to C++11. This is a peculiar choice if you’re choosing to “live at head”, as it implies “head lives in a cave.” Less facetiously, Abseil promises backward compatibility for at least 5 years. As a result, this means dealing with the least common denominator when making design choices, and Abseil is still not even specced to the bug-fixed version of C++11. Ignoring the lack of support for new language features, it is still possible to back-port much of the functionality of the newer Standard Library, and Abseil does cherry-pick some features. While this could create dependency problems, as complex features will require simpler features that also need to be back-ported, Abseil has shied away from this particular issue except where previously noted. The real problem is more systemic. Traditional library projects support clients stuck on old toolchains with pinned, maintained releases; it doesn’t matter if you can’t use the latest version of a thing if the version you care about is still around and receiving bug fixes. However, Abseil wants to treat a foundational library the same way you’d treat a throwaway web widget: zero support for older revisions. In order to achieve that, everyone may be held back.
Next up
Most of overview above focuses on the pieces of Abseil that are not directly relevant to build environment abstraction. To some extent, they can be ignored—or their use discouraged via code reviews and policies—if one is only interested in the other pieces. The critique portions of the overview, on the other hand, apply to Abseil as a whole.
In the next part in this series, I’ll be discussing those other pieces of Abseil and how they map to LUL.