Skip to content

PATTERN Cited by 1 source

Centralized forward declarations (Fwd.h per directory)

Shape

When a codebase chooses concepts/forward-declaration over #include as the default strategy for breaking C++ header include chains, engineers face a maintenance tax: forward declarations scattered across files hurt readability, searchability (grep "struct Foo" now returns N files), and rename safety. The naive answer — let each file forward-declare whatever it needs — doesn't scale beyond a small codebase.

Centralized forward declarations is Figma's convention:

  • One Fwd.h per directory. The file lists every forward declaration required by other files within that directory (or exported to dependents).
  • Every header in the directory #includes its Fwd.h. So forward- declared symbols are available to all headers without any per-file bookkeeping.
  • Source (.cpp) files never #include Fwd.h. Forward declarations are valuable in headers because they prevent including files from pulling in the forward-declared symbol's defining header transitively. Source files are leaves in the include graph — forward-declaring there saves nothing.

Example (from the Figma post):

// AnimalFwd.h
namespace Figma {
    struct Animal;
    struct Dog;
    struct Cat;
    enum struct AnimalType;
    using Feline = Cat;
};

Why it works

  • One place per directory to discover / rename. Fwd.h is the authoritative list; renames update one location.
  • Engineers don't have to think about forward declarations day-to-day. Directory-scoped Fwd.h means the pattern applies organically — adding a new type and using it from another file in the same directory "just works" without the author deciding header-vs-forward-declaration.
  • Directory = unit of cohesion. Figma's codebase is structured "something resembling modules, with each directory being built independently of others." Fwd.h rides on that — the types most frequently forward-declared from files in a directory are likely defined within the directory or a near peer.

Composition with the measurement gate

Fwd.h is the recipe engineers apply when systems/includes-py flags a transitive-byte regression in CI and the offending include is genuinely used. The include stays (so DIWYDU doesn't flag it), but the header's #include "foo.h" can often be replaced by #include "fooFwd.h" when only pointers / references / signatures of Foo are needed. The pattern makes the fix fast and conventional: no per-file forward-declaration debate.

Trade-offs

  • Readability-within-header. Forward-declaring symbols whose definitions live elsewhere can make a header harder to read in isolation: struct Dog : Animal { ... } where Animal is only a forward declaration requires the reader to go look it up.
  • Searchability. Centralizing mitigates but doesn't eliminate: struct Animal still appears in AnimalFwd.h and wherever Animal is truly defined.
  • Forward-declaration limits. Any user of Foo that needs sizeof, inheritance, or member access still needs the real header. Fwd.h doesn't eliminate heavyweight includes — it eliminates the avoidable ones.
  • Directory boundary friction. A type forward-declared in adirFwd.h but heavily used in bdir/ files is not served by the pattern in its pure form. Resolutions: export the Fwd.h publicly, or put the forward declaration in the consumer's directory too.

Where it composes in the wiki

Seen in

Last updated · 200 distilled / 1,178 read