January 27, 2023·5 min read·engineeringcc23standards

C23 type inference meets the ballot comments

January ballot review exposes the tension between generic C code and declarations that state an arithmetic contract explicitly.

Type inference changes what a declaration promises its reader. An explicitly unsigned variable establishes an arithmetic choice at the declaration. An inferred variable inherits that choice from its initializer, which might be a macro defined elsewhere. The January C23 ballot papers make this difference concrete through a dispute over auto, signed overflow, and the requirements of generic programming.

The January meeting agenda schedules virtual sessions for January 23 through 27 and identifies their purpose as resolving comments on CD 9899. A WG14 meeting index provides the committee's meeting records. Its agenda already advises a second committee-draft ballot because of the volume of comments. This is a review of a developing specification, so the papers discussed here describe objections and proposed resolutions, not a completed language release.

C was my first language, and its small vocabulary remains part of its appeal. That makes a proposal like type inference interesting to me because it changes the interpretation of ordinary declarations without introducing an elaborate new expression syntax. Its value depends on whether the rules preserve the information needed to understand the resulting program.

The arithmetic concern is specific

N3079, dated January 8 and written by Alex Gilding and Jens Gustedt, responds to national-body comments concerning type inference. The Swedish comment SE-001 argues that inferred types can conceal a consequential difference between signed and unsigned arithmetic. Its example initializes limit from MY_LIMIT and then compares an addition against the original limit as an overflow check.

The comment's concern is that the programmer expects an unsigned type while the macro actually supplies a signed type. Unsigned arithmetic has defined wraparound behavior. Signed overflow is undefined, so an optimizer need not preserve a check that depends on that overflow occurring. The error is in the assumed arithmetic contract, even though the declaration itself looks unremarkable.

An explicit unsigned declaration records that contract independently of the macro's type. Inference makes the initializer's type decisive. This matters whenever a value's representation is part of the algorithm, since changing a macro definition can then change more than the initial numeric value. A review that checks only the value can miss the changed arithmetic semantics.

The authors do not dismiss the safety concern. Their response argues that the comment neglects the feature's primary motivation, type-generic programming. They also distinguish the needs of different parts of the C community. A coding standard can restrict a construct that remains useful elsewhere, and the paper anticipates that MISRA could restrict the form shown in the objection.

My preference is to spell out a type when signedness is part of a calculation's correctness argument. Inference still has a reasonable role when the intended contract is to preserve the initializer's type. Those are different requirements, and a useful coding rule should say which requirement applies to the declaration being reviewed.

Why the keyword matters

Another comment, US-121, challenges the grammatical treatment of auto. The paper records Clang implementation feedback that C++ treats auto as a type specifier, while the proposed C approach combines a storage-class specifier with an omitted type. The comment asks for a type-specifier model and objects to reviving type omission with new semantics.

Gilding and Gustedt explain that auto already has a meaning in C and that its existing use has not been deprecated. Their response therefore treats the new behavior as an extension of what type omission means in a declaration containing that keyword. The spelling carries a compatibility obligation that a completely new keyword would not have.

The paper also describes the choice between auto and GCC's existing __auto_type. It says WG14 chose auto mainly for compatibility with C++. This creates a familiar standards problem. Sharing a spelling makes related languages easier to approach, but users can then expect a degree of semantic agreement that the underlying specifications have not established.

The distinction affects implementers as well as readers. A compiler must fit the feature into its declaration rules, and a standards document must describe it using the concepts already available in that language. Agreement on the short spelling does not settle how declarators participate in deduction.

Pointer declarators expose the boundary

US-122 demonstrates the gap with two declarations after an ordinary integer object. The simple inferred declaration takes the address as its initializer. The second declaration adds a pointer declarator, following a style familiar to C++ programmers. N3079 presents these forms as a disputed boundary in the C draft.

int i;
auto good = &i;

This short fragment illustrates the simple declaration discussed in the paper. It is draft-language syntax, not a claim that an existing C language mode accepts it. The initializer determines the pointer type, so the declarator does not need to repeat the pointer symbol to express the result.

The national-body comment asks for optional pointer and array declarators in inferred declarations. The authors explain that C++ deduction builds on rules developed for templates, while the C work initially followed the narrower behavior implemented by GCC's __auto_type. Extending the grammar therefore requires a corresponding account of deduction, not merely permission to insert another token.

N3079 discusses accommodating implementations that accept additional declarator forms by making their behavior implementation-defined and recommending agreement with C++. That is proposed wording in a ballot response. It cannot be treated as a portable guarantee that every familiar C++ declaration already has a specified C meaning.

Specification categories affect diagnostics

US-123 asks about a declaration with a braced initializer and about requirements placed beneath a Description heading. The practical question is whether a program violates a constraint or enters undefined behavior. These categories affect what users can expect an implementation to diagnose and what extensions a compiler can provide.

The response acknowledges the heading problem and proposes revised wording under Semantics. It also discusses narrowing initializer forms. This part of the paper shows why ballot resolution can change a feature materially even when its headline remains the same. The accepted grammar and the classification of unsupported forms determine what a compiler can promise to reject.

For me, the useful result of this review is a more precise distinction between preserving an expression's type and choosing a type for an algorithm. Generic code often needs the first operation. Arithmetic with a deliberate signedness requirement needs the second. January's public papers explain both motivations and identify the declarator rules that still need agreement. The agenda leaves further ballot work on the schedule, including a possible February continuation, so the appropriate description remains a language feature under detailed review.