Ch20.12: std::move, std::addressof, and the Utility Headers

中文(简体)

1. The Features Whose Absence You Cannot Work Around

The previous pages were about features that need infrastructure: a heap, an unwinder, a thread pointer. This page is worse, because it is about features that need nothing — no allocation, no runtime, no table, no OS — and were still not available where they were needed most.

::std::move casts to an rvalue reference. ::std::forward casts conditionally. ::std::swap is three moves. ::std::pair is two members. ::std::addressof takes an address. Five of the cheapest things in the standard library, and every one of them lives in <utility> or <memory>, neither of which was a required freestanding header until C++23 (20.6).

2. Why They Were Missing, and Why the Reason Is Not Technical

The freestanding header list was not designed. It was derived in 1998 from one question: which headers does the language itself need in order to be implementable? The answer was <cstddef> for size_t, <limits> and <cfloat> for the ranges, <cstdarg> for variadic functions — and then <new>, <typeinfo> and <exception>, because the language has new, RTTI and exceptions and those need library support. That is a list of the compiler's needs. It is not a list of what a program needs to be written at all, and nobody asked that question again for twenty years.

So <utility> failed for a reason that has nothing to do with <utility>: it was not part of the compiler's bootstrap set, so it was not on the list, so a conforming freestanding implementation did not have to provide it. The same non-reason covers <type_traits> (which was added later, in C++11, for a different purpose) and <iterator>, <ranges> and <memory>, which arrived in C++23 as "partial" — each with an official list of the bits you are allowed to expect.

Compare C, where the list is four headers and each one is genuinely compiler-owned. Nobody has ever needed to add a fifth, because the list was drawn from a principle — "what cannot be written in the language" — rather than from a snapshot of what one implementation happened to need in 1998.

3. The One You Can Write, and the One You Cannot

There is a real asymmetry among these five, and it is the most instructive thing on the page.

You can write move and forward yourself. They are casts; the definition is one line each, and a freestanding codebase that lacked <utility> simply defined its own in its own namespace. It is ugly, it duplicates the standard library, and it works. This is why the absence was survivable: where the feature is a cast, the missing header is an inconvenience.

You cannot write addressof. It has to defeat a user-defined operator&, which means it needs compiler magic (__builtin_addressof), and it is constexpr, which means it cannot be a runtime reinterpret_cast hack. There is no portable implementation — the standard library's own implementation is a builtin call with a reinterpret_cast fallback for pre-C++11 compilers. So for the whole period where ::std::addressof was not required in freestanding, a freestanding program had a language feature — taking the address of an object — that any class could quietly redefine, and no way to get the real address back (17.8 writes the program that makes &s == nullptr true).

That is the sharpest form of the rule this chapter keeps returning to: a standard feature that is not freestanding does not exist. Not "is inconvenient", not "needs a workaround" — does not exist. And the features where that bites hardest are not the big ones, because the big ones have workarounds you can see. They are the one-line ones that turn out to need the compiler.

C has had &x mean "the address of x" since 1972, in freestanding and hosted code alike, with no library involved. C++ let a class change what the operator means, and then took two decades to promise that the escape hatch would be there on the targets where nobody could otherwise provide it.

4. The Portability Trap This Creates

Here is the trap, and it is not a compiler bug:

  1. Your hosted toolchain ships <utility> and <memory> regardless of what the standard requires, because it was built for a desktop where they are always present.
  2. So your code compiles on your machine, with -ffreestanding or without it, and you conclude that the code is freestanding.
  3. Then you build for the board, on a toolchain whose C++ library is exactly the required subset, and the same file does not compile — or worse, links against a std::addressof that is not there.
  4. And nothing in the source told you which of the two worlds you were in, because there was no way to ask. The feature-test macros that answer the question (__cpp_lib_freestanding_*) are C++26, and C++26 was not published (20.6).

This is the answer to "why do you keep saying C++ is not designed for freestanding". Not because the language cannot compile without an OS — it can, and this book does. Because the standard gave you no way to write a program that is only freestanding and have the toolchain check it. You found out on the board.

5. What to Do About It

The practical policy, and the one this book follows: