Name n3759, alx-0070r1 - Add operator _Typeas Principles - Enable secure programming Category New operator. Author Alejandro Colomar History r0 (2025-11-14): - Initial draft. r1 (2025-12-02, n3759): - typeas() can be implemented as a macro. Description Certain type-generic macros enhance the (type) safety of functions. For example, there's MALLOC(): #define mallocarray(n, size) reallocarray(NULL, n, size) #define MALLOC(n, T) \ ( \ (typeof(T) *){mallocarray(n, sizeof(T))} \ ) which is used as int *p; p = MALLOC(42, int); where regular malloc(3) calls, depending on coding styles, are usually some of these: A) p = mallocarray(42, sizeof(*p)); B) p = (int *){mallocarray(42, sizeof(int))}; C) p = (int *){malloc(sizeof(int [42]))}; (Or combinations of those. Also, some styles use a cast instead of a compound literal.) The macro has several advantages over regular calls to malloc(3). The problems with the regular function calls are: A) - The type isn't explicit in the call. This makes it impossible to grep(1) for all allocations of a given type. It also makes it slightly less readable for the programmer, which needs to find the declaration of the pointer to check which type it has. - There's nothing that verifies that 'sizeof(*p)' is correctly specified. I tried GCC, Clang, and clang-tidy with a bogus call: p = mallocarray(42, sizeof(p)); and none of them complained about it. B) - The type within sizeof() is de-coupled from the type of 'p'. If the type of 'p' changes, and the programmer forgets to change the type within sizeof(), then the call will request an incorrect size. C) - The issues of B apply to C as well. - The size calculation may overflow size_t, producing a bogus (too low) size request, which would later result in a buffer overflow. All of these are solved through the MALLOC() macro. Problem However, this macro is still imperfect. The macro is defined as: #define MALLOC(n, T) \ ( \ (typeof(T) *){mallocarray(n, sizeof(T))} \ ) Nothing in the macro verifies that T is indeed a type. One could call by accident p = MALLOC(42, 7); and it would be accepted by the compiler. One could consider defining MALLOC() as #define MALLOC(n, T) \ ( \ (T *){mallocarray(n, sizeof(T))} \ ) Such a definition would make sure that T is a type. However, that wouldn't work for certain types. Consider the following code: int (*p)[7]; p = MALLOC(42, int [7]); That would expand to 'int [7] *', which is a syntax error: malloc.c:4:27: error: expected ‘)’ before ‘*’ token 4 | #define MALLOC(n, T) ((T *){mallocarray(n, sizeof(T))}) | ~ ^ malloc.c:11:13: note: in expansion of macro ‘MALLOC’ 11 | p = MALLOC(42, int [7]); | ^~~~~~ It could be solved with a more complex use of typeof(): #define MALLOC(n, T) \ ( \ (typeof(typeof((T){}) *){mallocarray(n, sizeof(T))}\ ) which could be wrapped as #define typeas(T) typeof((T){}) #define MALLOC(n, T) \ ( \ (typeas(T) *){mallocarray(n, sizeof(T))} \ ) This is indeed a valid implementation of _Typeas() as specified in this proposal. To make it easier for projects to use this level of safety, and also to raise awareness of it, it would be useful to provide this operator as part of the compiler. Simple compilers that don't want to grow their source code too much could decide to provide it as a pre-defined macro. A solution would be to add a new operator, inspired by _Alignas(). The operator would produce a type specifier, like typeof(), but it would require that the operand is a type name. The name of the operator would be _Typeas(). The macro MALLOC() could be defined as #define MALLOC(n, T) \ ( \ (_Typeas(T) *){mallocarray(n, sizeof(T))} \ ) Which would have the same guarantees that the current MALLOC() has, plus it would require that T is a type. Proposed wording Based on N3550. 6.4.2 Keywords @@ Syntax, p1 keyword: one of ... _Noreturn + _Typeas 6.5.2.1 Generic selection @@ Semantics, p3 The generic controlling operand, size expressions, -and typeof operators +and typeof and _Typeas operators contained in the type names of generic associations are not evaluated. 6.5.3.6 Compound literals @@ Constraints, p4 ... - SC typeof(T) ID = { IL }; + SC _Typeas(T) ID = { IL }; ... 6.5.5 Cast operators @@ Semantics, p5 Size expressions -and typeof operators +and typeof and _Typeas operators contained in a type name used with a cast operator are evaluated whenever the cast expression is evaluated. 6.6.1 Constant expressions :: General @@ Description, p4, footnote 117 The operand of a typeof (6.7.3.6), +_Typeas (6.7.3.6+1), sizeof, _Countof, or alignof operator is usually not evaluated (6.5.4.5). @@ p7 ... Cast operators in an integer constant expression only convert arithmetic types to integer types, except as part of an operand to the typeof operators, +_Typeas operator, sizeof operator, _Countof operator, or alignof operator. @@ p8 An arithmetic constant expression has arithmetic type and only has operands that are floating literals, named or compound literal constants of arithmetic type and integer constant expressions. Cast operators in an arithmetic constant expression only convert arithmetic types to arithmetic types, except as part of an operand to the typeof operators, _Typeas operator, sizeof operator, _Countof operator, or alignof operator. 6.7.1 Declarations :: General @@ Constraints, p3 EXAMPLE 1 The following are invalid, because the declared tag or enumeration constants are in a nested construct, rather than a declaration specifier of the declaration being of one of the given forms: struct { struct s2 { int x2a; } x2b; }; - typeof (struct s3 { int x3; }); + _Typeas (struct s3 { int x3; }); alignas (struct s4 { int x4; }) int; - typeof (struct s5 *); + _Typeas (struct s5 *); - typeof (enum { E6 }); + _Typeas (enum { E6 }); struct { void (*p)(struct s7 *); }; @@ p15 EXAMPLE 3 As declarations using constexpr are underspecified, the following has implementation-defined behavior because tokens within the declaration declare s which is not an ordinary identifier: - constexpr typeof(struct s *) x = 0; + constexpr _Typeas(struct s *) x = 0; 6.7.3.1 Type specifiers :: General @@ Syntax, p1 type-specifier: ... typeof-specifier + typeas-specifier @@ Constraints, p2 ... -- typeof specifier + -- typeas specifier @@ Semantics, p5 Specifiers for structures, unions, enumerations, atomic types, -and typeof specifiers +typeof specifiers, +and typeas specifiers are discussed in -6.7.3.2 through 6.7.3.6. +6.7.3.2 through 6.7.3.6+1. ... @@ Semantics, p12, footnote 138 ... This includes an int type specifier produced using -the typeof specifiers (6.7.3.6). +the typeof specifiers (6.7.3.6) +or the typeas specifier (6.7.3.6+1). 6.7.3.6 Typeof specifiers @@ Semantics, p4 ... Otherwise, they designate the same type as the type name with any nested -typeof specifier +typeof or typeas specifiers evaluated.151) ... @@ footnote 151 If the typeof specifier argument is itself -a typeof specifier, +a typeof or typeas specifier, the operand will be evaluated before evaluating the current typeof operator. This happens recursively until -a typeof specifier +a typeof or typeas specifier is no longer the operand. 6.7.3 Type specifiers @@ New section after 6.7.3.6 ("Typeof specifiers") +6.7.3.6+1 Typeas specifier +Syntax +1 typeas-specifier: + _Typeas ( type-name ) + +Semantics +2 The typeas specifier + applies the _Typeas operator + to a type name. + It designates the same type as the type name + with any nested + typeof or typeas specifiers + evaluated.XXX) + If the operand is a variably modified type, + the operand is evaluated; + otherwise, + the operand is not evaluated. + +3 The _Typeas operator + preserves all qualifiers. + +4 EXAMPLE + _Typeas(int) + main(void) + { + return 0; + } + is equivalent to this program: + int + main(void) + { + return 0; + } + +XXX) + If the typeas specifier argument + is itself + a typeof or typeas specifier, + the operand will be evaluated + before evaluating the current typeof operator. + This happens recursively until + a typeof or typeas specifier + is no longer the operand. 6.7.4.1 Type qualifiers :: General @@ Constraints, p6 If the same qualifier appears more than once in the same specifier-qualifier list or as declaration specifiers, either directly, -via one or more typeof specifiers, -via one or more typeof or typeas specifiers, or via one or more typedefs, the behavior is the same as if it appeared only once. ... 6.7.7.3 Array declarators @@ Semantics, p5 ... Where an array length expression is part of the operand -of the typeof or sizeof operators +of the typeof, _Typeas, or sizeof operators and changing the value of the array length expression would not affect the result of the operator, it is unspecified whether or not the array length expression is evaluated. ... 6.7.9 Type definitions @@ Semantics, p3 ... Any array size expressions associated with variable length array declarators -and typeof operators +and typeof and _Typeas operators are evaluated each time the declaration of the typedef name is reached in the order of execution. ... 6.8. Statements and blocks :: General @@ Semantics, p3 ... The initializers of objects that have automatic storage duration, and any size expressions -and typeof operators +and typeof and _Typeas operators in declarations of ordinary identifiers with block scope, are evaluated and the values are stored in the objects (the representation of objects without an initializer becomes indeterminate) each time the declaration is reached in the order of execution, as if it were a statement, and within each declaration in the order that declarators appear. 6.9.1 External definitions :: General @@ Constraints, p4 ... -- or, part of the operand of - any typeof operator + any typeof or _Typeas operators whose result is not a variably modified type. @@ Semantics, p6 ... If an identifier declared with external linkage is used in an expression (other than as part of the operand of -a typeof operator +a typeof or _Typeas operator whose result is not a variably modified type, part of the controlling expression of a generic selection, part of the expression in a generic association that is not the result expression of its generic selection, or part of a sizeof, _Countof, or alignof operator that is an integer constant expression), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one. 6.9.2 Function definitions @@ Semantics, p10 On entry to the function, the size expressions of each variably modified parameter -and typeof operators +and typeof and _Typeas operators used in declarations of parameters are evaluated and the value of each argument expression is converted to the type of the corresponding parameter as if by assignment. ... 7.22.3 The nullptr_t type @@ Description, p3 ... -- as the operand of alignas, - sizeof + sizeof, - or typeof + typeof, + or _Typeas operators, A.2.2 Keywords A.3.2 Declarations J.6.3 Particular identifiers or keywords M.2 Sixth edition Index ## Update.