October 12, 2020·5 min read·engineeringcppclanglinkagecompatibility

Clang 11 requires a tag for C++ classes beyond C compatibility

Clang 11 applies P1766R1's restriction on unnamed classes with typedef names for linkage. Giving the class a tag resolves a problem caused by learning its identity too late.

A compiler can need a class's linkage before it reaches the typedef name written after the closing brace. C++ allows declarations inside the class to trigger that need. Clang 11 tightens its treatment of anonymous classes with typedef names for linkage, following Richard Smith's P1766R1. The release notes explain the resulting source change for the October 12 release.

This is a small-looking compatibility change with a specific compiler problem behind it. The familiar C-style declaration places the typedef name after the class body. More complex C++ declarations can require the compiler to establish the type's identity while it is still reading that body.

I find such changes easier to assess when the implementation constraint is explicit. A warning about a familiar declaration can look arbitrary until the ordering problem becomes visible. Naming the class earlier gives both the reader and the compiler the relevant identity before processing its members.

The problematic declaration names the type late

The release notes use a class with a member function to illustrate the affected form. The typedef supplies a name, but the class itself has no tag.

typedef struct {
    int value() { return 7; }
} Result;

The recommended C++ form gives the class its own name. The tag appears before the body, so processing the members no longer depends on discovering a typedef afterward.

struct Result {
    int value() { return 7; }
};

The change is about the declaration's structure. It preserves the idea of a class named Result while supplying that name at the beginning of its definition. It does not require removing the member function that made the original anonymous form unsuitable.

For shared C and C++ source, a tag can coexist with the typedef. Conditional compilation can retain the language-specific member while both modes see the early tag.

typedef struct Result {
    int code;
#ifdef __cplusplus
    int value() const { return code; }
#endif
} Result;

The C compilation sees the data member and the tagged structure. C++ compilation also sees the member function. The example demonstrates a naming repair, not a blanket guarantee that any larger shared header is compatible in both languages.

Why an early identity matters

P1766R1 explains that several constructs inside an anonymous class can trigger a linkage calculation. The compiler may not yet know whether a later declarator supplies the special typedef name used for linkage. The paper shows why merely encountering the typedef keyword does not settle that question.

A declaration can introduce a pointer typedef instead of a typedef naming the class itself. The final declarators determine the relevant relationship. An implementation cannot infer the whole answer from the beginning of the declaration.

The distinction also helps separate a type from a particular variable declaration. A tag identifies the class as the definition begins. Declarators following the body can then introduce the objects or aliases that use it without carrying the responsibility for establishing that earlier identity. This gives the declaration a more direct order of explanation. It defines the named type before describing what the surrounding declaration does with it.

The original anonymous form creates a dependency on source that has not been read yet. The compiler needs information about the type to process a construct inside it, while the syntax supplies that information later. Because the problem is structural, a local naming change can address it directly.

The paper also relates the issue to modules and merging definitions. An implementation can already know about another definition without that definition being reachable in the current source context. Establishing that the new class corresponds to an existing one needs a tractable identity rule.

The restriction keeps the special anonymous-class mechanism within a simpler set of uses. A C++ class with richer behavior can instead have a tag from the start. This avoids making the delayed naming mechanism accommodate every possible class-body operation.

A defect resolution can affect older modes

Clang's notes identify P1766R1 as a defect-report resolution applied retroactively across C++ standard modes. That means retaining an older -std choice does not necessarily preserve the compiler's previous acceptance of this construct.

This is a useful distinction in compiler compatibility. Some changes implement new facilities selected by a language mode. Other changes revise an implementation's treatment of existing source because the language defect has a new resolution.

For a project encountering the diagnostic, the first question is therefore what the declaration intends. If it defines a C++ class, naming that class directly expresses the intention. If it is shared source, retaining a typedef with a tag can preserve the declaration pattern used by the C side.

The repair also improves the source's local explanation. A reader sees the class name before encountering member declarations. That can make references to the type easier to follow even apart from the compiler's linkage requirements.

A suppression has a narrower effect

The release notes document an extension-warning control for cases newly diagnosed by Clang 11. Disabling that warning changes what the build reports. It does not give the declaration an earlier tag or remove the implementation issue the paper describes.

Some affected forms were already rejected by previous Clang versions, depending on when linkage became necessary. Consequently, the change is not simply a universal transition from accepted code to rejected code. It makes treatment more consistent across constructs with the same underlying naming problem.

A source repair is more informative when the intended type can be named directly. It removes dependence on the special anonymous-class linkage rule. The resulting declaration also gives other implementations less delayed information to reconcile.

C++ codebases often contain several historical declaration styles, which is one reason I find the language demanding to read. This change has a practical advantage when it replaces an inherited style with a declaration that states the type's identity at the point where its definition begins.

For the shared-header example, the important result is that the typedef remains available while the tag establishes the class name early. For the C++-only example, the direct class definition provides the same clarity without the extra typedef. Both repairs address the linkage-order problem that motivated the restriction.