Namespaces and includes¶
Two questions come up as soon as you start writing Au code in earnest: which headers to include, and how to bring Au’s names into scope. This page gives our recommendations and the reasoning behind them. Our code examples follow these conventions throughout, so you can see them applied to real programs.
#include lines¶
Every file that uses Au will include "au/au.hh", plus a collection of individual files from one or
more of:
"au/units/...""au/units/literals/...""au/constants/..."
Sometimes, this can seem like a large number of files. However, we believe experience shows this to be a strongly justified tradeoff. Consider the following:
-
Giving each unit, literal, and constant its own file empowers users to include only what they need. Since accumulating these objects is the main cause of slow compilation in units libraries, this maximizes compile time performance.
-
Many other libraries try to “group” these items into related units: whether by system of measurement (e.g., “SI”), or by dimension (e.g., “length”). We consider this a clear design mistake. Not only does this harm compile time performance, but it makes the headers harder to guess — and, therefore, it makes the library harder to use.
The pattern should quickly become clear: "au/au.hh" for the core, and one easily guessable
header for each specific unit, literal, or constant that you actually use.
Optional feature headers¶
A few pieces of functionality live behind their own header, because not every project wants to pay for them:
| Header | Provides |
|---|---|
"au/io.hh" |
operator<<, for printing quantities to a stream |
"au/std_format.hh" |
std::format support |
"au/testing.hh" |
Utilities for writing googletest tests |
This is the same principle as above, applied to features rather than units. "au/io.hh" is the
one you’ll meet first, and it makes the point well: it pulls in the C++ streams machinery, which is
famously expensive, and which many embedded projects avoid entirely. Charging that cost to every
Au user — including those who never print a quantity — would be a poor trade. So we don’t:
you include it when you want it, and otherwise you never pay. (We take this seriously enough
that our single-file packages come in noio variants with the
streaming support stripped out.)
These headers are additions to "au/au.hh", not replacements for it: "au/io.hh" teaches the
library how to print, but it doesn’t bring in the core. Our examples that print a result include
both.
Note
Under Bazel, each of these is a separate dependency as well as a separate header —
@au//au:io, @au//au:std_format, @au//au:testing — so you’ll need to add it to your
deps. Under CMake, the single Au::au target provides all of them, and you only need the
#include. See the installation instructions for the full table.
Individual using statements¶
When code is within an implementation file (.cc, .cpp, etc.), we recommend giving every Au
object a “using declaration”, making its name visible within that file. This is the same convention
that Googletest recommends.
The downside is that this section of declarations can become lengthy. If you’d rather avoid this,
it’s fine; we’ve designed Au so that even the fully qualified names are as concise as possible (just
an au:: prefix: four characters). However, we believe the benefits win out on balance.
-
The prefix may be small, but Au is designed for composability, so it can be repeated many times. Compare
speed.in(au::kilo(au::meters) / au::hour)tospeed.in(kilo(meters) / hour): the latter has notably better readability and flow. -
The cost is small: it only affects one place in the file, and the location is predictable.
-
The more times you use a name, the more the
usingdeclaration pays off.
What you will not find in our examples is using namespace au;. We don’t recommend it, so we
don’t model it:1 au:: is deliberately two characters, and a directive that drags in every unit,
maker, and symbol in the library is a poor trade for saving them. (It’s the same instinct that
keeps using namespace std; out of most codebases. std:: is short for the same reason au:: is,
and in both cases the directive gives up a great deal of clarity to save very little typing.)
Header files cannot take this approach¶
The above advice applies to implementation files (.cc, .cpp, etc.) only. Header files
(.hh, .hpp, etc.) are a different story altogether.
The core reason is that #include is simple textual inclusion in C++, and it works transitively.
This means any names we expose in a header file will silently leak into every translation unit
that includes it: an unbounded set that grows over time. This problem is known as “namespace
pollution”, and experience has shown that it causes a litany of problems.
-
Dependencies become entangled and hard to extract.
-
It becomes hard to understand where names come from.
-
The build can break due to name collisions.
So we can’t use using declarations at namespace scope in a header file. Instead, there are two
alternatives that avoid this problem.
-
Qualify at the point of use —
au::QuantityD<au::Meters>. This is the default. -
Scope the
usingdown to a block where a name would otherwise repeat awkwardly. Ausingdeclaration inside a function body (or any other block) expires at the closing brace and leaks nowhere. This is the right tool for unit symbols in a header’s inline function, whereau::symbols::radat every mention would drown the formula:
Unit aliases¶
Au has excellent composability for objects. You can write (kilo(meters) / hour)(100), for
example, and it will automatically create the unit for “km/h” on the fly. Unfortunately, this
composability can’t extend to type names. For this same example unit, if we want to declare
a quantity variable, all of our options are a little bit awkward. They either need type traits, or
decltype:
QuantityF<decltype(Kilo<Meters>{} / Hours{})> speed;
QuantityF<UnitQuotient<Kilo<Meters>, Hours>> speed;
We can make this easier to read by just defining a type alias for the unit — and here, too, either spelling will do:
// Either of these will do; pick one.
using KiloMetersPerHour = decltype(Kilo<Meters>{} / Hours{});
using KiloMetersPerHour = UnitQuotient<Kilo<Meters>, Hours>;
QuantityF<KiloMetersPerHour> speed;
All three of these QuantityF declarations refer to the exact same type, just with different
spelling. Which one to reach for is a matter of taste. decltype lets you reuse the same
arithmetic operators you already know from the object world, at the cost of the keyword;
UnitQuotient names the operation outright, at the cost of learning and using a visually clunky
trait.
A type alias is a different thing from a using declaration, and the rules are correspondingly
looser: it introduces one name you chose, rather than importing a set of names you did not. That
makes it fine at namespace scope even in a header. That said, we recommend the same instincts as
above:
-
If you’re in an implementation file (
.cc,.cpp, etc.), use them without worrying about it. -
If you’re in a header file (
.hh,.hpp, etc.), a unit alias at namespace scope is safe, but keep in mind that it becomes part of your public API. Every consumer of the header will see it, and may come to depend on it. The Google C++ Style Guide puts it well:Don’t put an alias in your public API just to save typing in the implementation; do so only if you intend it to be used by your clients.
So: if the alias is for your consumers, give it a descriptive name and treat it as the interface it is. If it’s only for your own convenience, hide it inside a function or class.
Summary¶
Every project has its own style, conventions, and rules. We’ve provided the conventions here as a good default style for using Au, and explained the reasons that we recommend them. These aren’t hard and fast rules that we intend to force on all projects; they simply convey our vision for how to use the library ergonomically. We hope users will find them useful.
Much of this guidance comes from experience with large C++ projects, where the costs of namespace pollution compound most visibly. That doesn’t mean it’s for large projects only. Remember that “small” projects have a well known habit of becoming larger than anyone originally envisioned! These habits are far cheaper to adopt early than to retrofit later.
-
We do use
using namespace au;in the canonical “quickstart” godbolt link from our README, because the goal of that page is to make it as frictionless as possible to write Au code. It’s not intended to showcase landable code; it’s intended to make it as easy as possible to see how the library behaves in some situation, or to use it to get some result. To further illustrate this point, note that our godbolt link also includes every unit, constant, and literal in the whole library, even though we would never recommend this in production code, because the compile time cost doesn’t scale. ↩