Chapter 18: Type-generic programming
The introduction shows that C already has widespread type-generic features, then contrasts the fixed-type limits of _Generic with C23 type inference.
After this chapter you will be able to
- Existing type-generic features. Explain that type-genericity is already widespread in C, including operators that work with multiple types, promotions and conversions, macros, variadic functions, and pointers.
- Generic selection and fixed types. Describe how _Generic can choose an action at compile time according to a fixed set of types, and why that restriction can produce many cases.
- Expression-based type inference. Explain that C23 type inference uses auto, typeof, and typeof_unqual to derive type information from expressions, which can avoid complicated lists of type-specific cases.
- Operators as type-generic features. Explain why an expression such as a*b+c can be type-generic, and give examples of the types supported by == and !=.
- Conversions to requested types. Explain that an arithmetic argument, assignment value, or initializer can be converted to the type required by its destination, as when cosf converts the int argument 1 to float.
- Promotions and default argument conversion. Distinguish integer promotion from default argument conversion for floating-point values, including why the float result of cosf becomes double when passed to printf.
- Common arithmetic result type. Trace how default arithmetic conversion chooses a common type for binary arithmetic, using -1 + 1U to explain why its result is unsigned int zero.
- Type-generic macro expressions. Explain how a macro can produce a type-generic arithmetic expression by substituting tokens and relying on C’s usual arithmetic conversions. Describe why the color-channel example can produce different result types for different argument types.
- Statement-like macro safeguards. Explain how the described macro groups statements for use like a void function, limits repeated argument evaluation, and reduces—but does not eliminate—name conflicts.
- Variadic argument conventions. Explain how ... lets a variadic function accept an arbitrary list of arguments and why the expected number and types mostly depend on a convention between the implementor and the user.
- Conversions and argument checking. Distinguish the conversions that apply to variadic arguments from the checking available for some variadic interfaces, and explain the safety concern for user-specified functions.
- Function-dependent generic algorithms. Explain how a function pointer lets an algorithm depend on another function, using the compar parameter of qsort or bsearch as an example.
- Void-pointer comparison interfaces. Explain how the comparison functions used by qsort and bsearch use void-pointer parameters for different object types, and describe the resulting convenience and type-safety tradeoff.
- Type-generic math interfaces. Explain how <tgmath.h> presents operations such as cosine through one type-generic macro rather than separate interfaces for float, double, and long double.
- Const-preserving pointer interfaces. Explain how the C23 type-generic interfaces for certain library functions preserve the const qualification of pointer arguments, and contrast this with calling the function interface directly.
- Type-directed generic selection. Explain how _Generic chooses an expression from type-specific alternatives and how a macro can use the chosen function pointer to call the appropriate function.
- Compile-time result type. Distinguish the result type of _Generic from that of the conditional operator, and explain the optimization opportunity when _Generic selects inline functions.
- Validity of every alternative. Explain why every expression in a _Generic selection must be valid, including alternatives that will not be selected, and describe how the examples avoid an invalid call or pointer conversion.
- Controlling type matching rules. Describe the type adjustments applied to a _Generic controlling expression and state the restrictions on its type alternatives.
- Purpose of type inference. Explain that C23 can infer a type from an expression or a type name, helping avoid a combinatorial explosion of different cases.
- Initializer-based type inference. Explain that C23 auto infers a variable’s type from its initializer, and describe how this gives variables in the SWAP macro types suited to a particular call.
- Type-consistent auxiliary variables. Justify using auto for an auxiliary variable initialized from another variable, even outside a generic macro.
- Unevaluated types in macros. Describe how typeof lets a macro use an argument’s type where a type name is required without evaluating that argument in the type-compatibility example.
- Multiparameter function selection. Explain how combining typeof with _Generic can select a power function based on argument types, and why this approach becomes complex.
- Return-type casting alternative. Describe how casting the result of a wide-type function can simplify a type-generic interface, including the stated tradeoff.
- Expression Macros and Compound Expressions. Explain why a do-while macro cannot replace an expression, and describe how gcc’s compound-expression extension makes a block usable as an expression.
- Single Evaluation in Maximum Macros. Explain the repeated-evaluation problem in a direct maximum macro and how a compound expression avoids it.
- Mixed Signedness Maximum Comparison. Trace why a direct comparison can give the wrong maximum for signed and unsigned arguments, and describe the sign-aware comparison strategy presented.
What it builds on
- Operands and operators (chapter 4) · The use of operands and operators in expressions
- Implicit conversions (chapter 5) · The basic idea of implicit conversions
- A detour: Variadic functions (chapter 17) · The earlier discussion of variadic functions.
- Arithmetic (chapter 4) · The earlier treatment of arithmetic operations.
- Unsigned integers (chapter 5) · The earlier treatment of unsigned integers.
- How function-like macros work (chapter 17) · How function-like macros substitute tokens
- Declarations (chapter 2) · Declarations
- Definitions (chapter 2) · Definitions
Section by section
Introduction
The introduction shows that C already has widespread type-generic features, then contrasts the fixed-type limits of _Generic with C23 type inference.
- Existing type-generic features: Explain that type-genericity is already widespread in C, including operators that work with multiple types, promotions and conversions, macros, variadic functions, and pointers.
Why it matters: It helps you recognize generic behavior in C features you may already use. - Generic selection and fixed types: Describe how _Generic can choose an action at compile time according to a fixed set of types, and why that restriction can produce many cases.
Why it matters: It explains both what generic selection offers and why it may become cumbersome. - Expression-based type inference: Explain that C23 type inference uses auto, typeof, and typeof_unqual to derive type information from expressions, which can avoid complicated lists of type-specific cases.
Why it matters: It provides the introduction’s alternative to proliferating _Generic cases.
Key terms: type-genericity, operators, type promotions and conversions, macros, variadic functions, function pointers, _Generic, generic selection, compile time, fixed set of types
18.1 Inherent type-generic features in C
Introduces a selective discussion of C’s inherent type-generic features and the issues it will highlight.
18.1.1 Operators
This subsection explains how C operators provide type-generic expressions and how they handle operand types.
- Operators as type-generic features: Explain why an expression such as a*b+c can be type-generic, and give examples of the types supported by == and !=.
Why it matters: It explains how ordinary C expressions can work across types without separate type-specific syntax. - Binary operand type handling: Distinguish binary operations that use conversions to make operand types agree from the listed operators that can accept different operand types without conversion.
Why it matters: It prevents treating every binary operator as though its operands must start with, or be converted to, the same type.
Key terms: type-generic, operators, conversions, shift operators, object pointer addition, array subscripting
18.1.2 Default promotions and conversions
This subsection explains how C converts arithmetic values to required types and chooses a common type for binary arithmetic.
- Conversions to requested types: Explain that an arithmetic argument, assignment value, or initializer can be converted to the type required by its destination, as when cosf converts the int argument 1 to float.
Why it matters: These conversions help explain why one function can be called with more than one arithmetic argument type. - Promotions and default argument conversion: Distinguish integer promotion from default argument conversion for floating-point values, including why the float result of cosf becomes double when passed to printf.
Why it matters: The distinction explains why an expression or function call may use a wider type than the value originally had. - Common arithmetic result type: Trace how default arithmetic conversion chooses a common type for binary arithmetic, using -1 + 1U to explain why its result is unsigned int zero.
Why it matters: The example shows why mixed signed and unsigned arithmetic can give a result that is surprising at first glance.
Key terms: conversion, argument type, parameter type, promotion, default argument conversion, variadic function, float, double, default arithmetic conversion, binary arithmetic operators
18.1.3 Macros
This subsection shows how C macros can support type-generic expressions, declarations, and statement groups, and explains the tradeoffs of statement-like macros.
- Type-generic macro expressions: Explain how a macro can produce a type-generic arithmetic expression by substituting tokens and relying on C’s usual arithmetic conversions. Describe why the color-channel example can produce different result types for different argument types.
Why it matters: It explains how one macro expression can work with more than one arithmetic type. - Macro-generated type declarations: Describe how macros can provide type declarations and definitions, and explain the intended syntax of the shown invocations.
Why it matters: Type-generic macro use is not limited to expressions. - Statement-like macro safeguards: Explain how the described macro groups statements for use like a void function, limits repeated argument evaluation, and reduces—but does not eliminate—name conflicts.
Why it matters: These safeguards explain both the usefulness and the maintenance cost of statement-like macros.
Key terms: preprocessor, function-like macros, default arithmetic conversion, type-generic expressions, type declarations, definitions, macro invocation, do ... while(false), auxiliary variables, macro parameters
18.1.4 Variadic functions
This subsection explains how variadic functions provide type-generic interfaces, while highlighting argument conversions and checking limits.
- Variadic argument conventions: Explain how ... lets a variadic function accept an arbitrary list of arguments and why the expected number and types mostly depend on a convention between the implementor and the user.
Why it matters: The flexibility of a variadic call depends on both sides agreeing about its arguments. - Conversions and argument checking: Distinguish the conversions that apply to variadic arguments from the checking available for some variadic interfaces, and explain the safety concern for user-specified functions.
Why it matters: Conversions and limited checking affect how safely a variadic interface can be used.
Key terms: variadic functions, ..., type-generic interfaces, narrow integers, float, format string, user-specified functions
18.1.5 Function pointers
This section explains how function pointers let algorithms such as qsort and bsearch work with different types by accepting a comparison function.
- Function-dependent generic algorithms: Explain how a function pointer lets an algorithm depend on another function, using the compar parameter of qsort or bsearch as an example.
Why it matters: A comparison function lets the same library algorithm serve different types.
Key terms: function pointers, compar, qsort, bsearch
18.1.6 void pointers
This subsection explains how void-pointer parameters make comparison interfaces type-generic, while leaving type safety to the user.
- Void-pointer comparison interfaces: Explain how the comparison functions used by qsort and bsearch use void-pointer parameters for different object types, and describe the resulting convenience and type-safety tradeoff.
Why it matters: It explains both why one comparison interface can serve different object types and why callers must check that they use it safely.
Key terms: void pointers, qsort, bsearch, BASE, target qualification, type safety
18.1.7 Type-generic C library functions
This subsection shows how C library type-generic interfaces select type-appropriate behavior, preserve pointer const qualification, and sometimes require more than size-based selection.
- Type-generic math interfaces: Explain how <tgmath.h> presents operations such as cosine through one type-generic macro rather than separate interfaces for float, double, and long double.
Why it matters: It lets callers use one familiar operation across several numerical types. - Const-preserving pointer interfaces: Explain how the C23 type-generic interfaces for certain library functions preserve the const qualification of pointer arguments, and contrast this with calling the function interface directly.
Why it matters: Preserving const qualification helps prevent a returned pointer from being used to modify data that should remain unchanged. - Atomic type selection limits: Distinguish the selection problem for C11 <stdatomic.h> type-generic functions from the finite, sometimes size-selected sets represented by <tgmath.h> macros.
Why it matters: It shows why a selection technique suitable for some math macros does not cover every type-generic library interface.
Key terms: <tgmath.h>, type-generic macro, cos, <math.h>, <complex.h>, C23, const-contract, const-qualified, function interface, <stdatomic.h>
18.2 Generic selection
This section explains how C’s _Generic selects expressions by type, how to use it in type-generic interfaces, and the constraints that make those selections valid.
- Type-directed generic selection: Explain how _Generic chooses an expression from type-specific alternatives and how a macro can use the chosen function pointer to call the appropriate function.
Why it matters: It lets one macro interface select among functions for different argument types. - Conversions in combined dispatch: Describe how the min macro uses the sum of two arguments to select a function based on their promoted or converted types.
Why it matters: The type of a combined expression can guide selection when a generic operation takes two arguments. - Compile-time result type: Distinguish the result type of _Generic from that of the conditional operator, and explain the optimization opportunity when _Generic selects inline functions.
Why it matters: A type-specific choice can retain the selected result type instead of forcing all alternatives into one combined type. - Validity of every alternative: Explain why every expression in a _Generic selection must be valid, including alternatives that will not be selected, and describe how the examples avoid an invalid call or pointer conversion.
Why it matters: A generic macro must remain valid even where a particular alternative will never be chosen. - Controlling type matching rules: Describe the type adjustments applied to a _Generic controlling expression and state the restrictions on its type alternatives.
Why it matters: Matching the adjusted type prevents unreachable or ambiguous alternatives. - Nonfunction generic results: Describe how _Generic can select a value rather than a function pointer, and explain the maxof controlling-expression trick for accepting a type or an expression.
Why it matters: _Generic can preserve a constant-expression result instead of requiring a function call.
Key terms: _Generic, controlling expression, default, function pointer, type-generic macro, min, argument promotions, conversion, result type, ternary operator
18.3 Type inference
The section introduces C23 type inference as a way to avoid handling a combinatorial number of type cases.
- Purpose of type inference: Explain that C23 can infer a type from an expression or a type name, helping avoid a combinatorial explosion of different cases.
Why it matters: This gives a reason to use type inference without requiring you to know its syntax yet.
Key terms: type inference, expression, type name, combinatorial explosion
18.3.1 The auto feature
This section explains how C23’s auto infers variable types from initializers and why that helps type-generic macros and type-consistent local declarations.
- Initializer-based type inference: Explain that C23 auto infers a variable’s type from its initializer, and describe how this gives variables in the SWAP macro types suited to a particular call.
Why it matters: Inferred declarations let the same macro work with different non-array object types without assuming a particular base type. - Macro invocation safeguards: Describe why SWAP wraps its code in a do ... while(false) loop and captures its arguments.
Why it matters: These safeguards make calling the macro more like calling a function. - Macro local name collisions: Explain why the macro uses swap-prefixed local names and why that convention cannot eliminate clashes with caller code.
Why it matters: A macro’s internal names can unexpectedly change what an argument refers to. - Type-consistent auxiliary variables: Justify using auto for an auxiliary variable initialized from another variable, even outside a generic macro.
Why it matters: Type inference can keep related declarations in sync as code changes.
Key terms: auto, initializer, SWAP macro, swap_p1, swap_p2, swap_tmp, do ... while(false), macro parameters, evaluated once, swap-prefixed names
18.3.2 The typeof feature
Explains when typeof and typeof_unqual are useful beyond auto, especially for type-generic macros, and shows the complexity and tradeoffs of combining them with _Generic.
- Choosing auto for declarations: Explain why the section recommends auto rather than typeof for ordinary variable declarations.
Why it matters: It helps you choose the simpler type-inference feature when declaring variables. - Unevaluated types in macros: Describe how typeof lets a macro use an argument’s type where a type name is required without evaluating that argument in the type-compatibility example.
Why it matters: A type-checking macro can inspect argument types without causing the arguments’ effects to happen. - Multiparameter function selection: Explain how combining typeof with _Generic can select a power function based on argument types, and why this approach becomes complex.
Why it matters: It shows both the power and the maintenance cost of selecting functions from multiple argument types. - Return-type casting alternative: Describe how casting the result of a wide-type function can simplify a type-generic interface, including the stated tradeoff.
Why it matters: It offers a simpler generic design when exhaustive type-based dispatch would be cumbersome. - Compile-time type traits: State what type traits can do when typeof and _Generic are combined.
Why it matters: Type traits let generic code adapt to properties of its argument types.
Key terms: auto, typeof, qualifiers, generic selection, type compatibility, SWAP, _Generic, pown, pow, multiparameter type matching
18.4 Anonymous functions
The section compares compiler extensions for expression-like macros and uses a maximum macro to show why single evaluation and mixed signedness matter.
- Expression Macros and Compound Expressions: Explain why a do-while macro cannot replace an expression, and describe how gcc’s compound-expression extension makes a block usable as an expression.
Why it matters: It explains why a statement-shaped macro is insufficient when a caller needs a value. - Anonymous Function Scope and Results: Describe how the block closure and lambda examples use an immediate call, and distinguish their described access to surrounding variables and their results.
Why it matters: The examples show how expression-like wrappers differ in what surrounding context their bodies can use. - Single Evaluation in Maximum Macros: Explain the repeated-evaluation problem in a direct maximum macro and how a compound expression avoids it.
Why it matters: A maximum macro should not unexpectedly evaluate a supplied expression twice. - Mixed Signedness Maximum Comparison: Trace why a direct comparison can give the wrong maximum for signed and unsigned arguments, and describe the sign-aware comparison strategy presented.
Why it matters: Avoiding double evaluation alone does not make a maximum macro correct for mixed signed and unsigned values.
Key terms: do { ... } while(false), compound expression, void expression, block closure, lambda, captures, MAX macro, signedness, implicit integer conversions, UINT_MAX
18.5 Summary
Concepts explained in this chapter
- type-generic interfaceType-generic programming is a programming technique that selects or adapts operations according to the types of values involved.
- type-generic macroA type-generic statement is a statement-form macro expansion whose operations can adapt to the types of its arguments or associated objects.
- _GenericA generic selection is a C11 expression construct that selects one expression from a set based on the type of a controlling expression.
- type inferenceType inference is the compile-time deduction of type information from an expression or declaration without explicitly writing the complete type.
- null pointer constantA pre-C23 null pointer constant is a compile-time constant expression of integer type and value zero or an expression of type void* that can be converted to a null pointer.
- nullptr_tnullptr_t is the C23 distinguished type of the nullptr value and is distinct from every pointer type.
- typeof operatortypeof is a C type operator that derives a type from an expression or type name without evaluating an expression operand.
- lambdaAn anonymous function is a function construct without a program-level name, provided by a C extension for embedding callable behavior in expressions or macros.
- autoauto is a C23 type-inference feature that allows an object's type to be inferred from its initializer.
- typeof_unqualtypeof_unqual is a C23 type operator that derives the unqualified version of a type from an expression or type name.
- closureA block closure is an anonymous function construct that can access variables from its surrounding context, subject to capture and modification rules.
- compound expressionA compound expression is a compiler extension that groups statements and expressions into a single expression whose value is the value of its final expression.
- regex groupingA capture is a lambda specification identifying variables from the surrounding scope that are made available inside the lambda.
- type traitA type trait is a compile-time operation that queries a property of a type and produces a result representing that property.
- naming standardA naming convention is a systematic rule for choosing identifiers so that related entities are recognizable and less likely to collide.
- do-while(false) trickA do-while macro wrapper is a macro pattern that encloses multiple statements in a single statement-like construct using a do loop whose condition is false.
- preprocessorThe C preprocessor is a translation phase that processes preprocessing directives and expands macros before compilation.
- atomic typeA type specifies the kind of values an entity can represent and the operations applicable to those values.
- implicit conversionAn implicit conversion is an automatic C conversion that changes a value from one type to another according to the language's conversion rules.
- integer promotionInteger promotion is the implicit conversion of a narrow integer type to an integer type before certain C expressions are evaluated.
- operatorAn operator is a C symbol or symbol sequence that specifies an operation on one or more operands.
- variadic argumentsA variadic parameter is the ellipsis-based parameter portion of a C function declaration that accepts additional arguments.
- comparison functionA comparison function is a caller-supplied function that determines the ordering or equality of two data elements for an algorithm such as sorting or searching.
- void pointerA void pointer is a C pointer to void that can hold the address of an object of any object type, subject to valid conversions and qualification rules.
- function pointer typeA function pointer is a C pointer value that designates a function and can be used to invoke or pass that function indirectly.
- integer constant expressionAn integer constant expression is a C expression whose value can be determined during translation using only permitted constant operands and operations.
- macro invocationA macro invocation is source text consisting of a function-like macro name followed by parentheses containing its arguments.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
