Add type-safe minimum and maximum type-generic macros

Jens Gustedt, INRIA and ICube, France

2025-09-07

target

integration into IS ISO/IEC 9899:202y

document history

C document ID: C4172

document number date comment
n3543 202504 original proposal
along the lines vote in Brno for integer types 22/0/6
n3707 202509 remove floating types
place in <stdlib.h>
make the interfaces unsequenced

1 Introduction

Generic features to compute minimum and maximum value of two given values is notoriously challenging in C. Motivating examples for this proposal are the following

constexpr size_t n = SIZE_MAX;
static double A[stdc_max(n, 1)];
enum { small = stdc_min(something, -1), };

That is, minimum and maximum features that are usable where a integer constant expression is needed and that are able to mix comparison of signed (1 and -1) and unsigned (n and perhaps something) without hickup.

The difficulties with such features are as follows

2 Approach

We propose to add type-generic macros that solve all these problems for all integer value and type combinations. Namely:

In particular the choice for the minimum differs from the usual arithmetic conversions. If one type is signed and the other is unsigned the macro stdc_min chooses the signed type for the result.

The proposed features have to be implemented side effect free and thus, seen as function interfaces, they should be pure in the CS sense of the term. So, as we have already have done for the bit manipulation macros, for these new interfaces we add the [[unsequenced]] attribute.

For convenience, we propose to add the features to the <stdlib.h> header, 7.25.7 “Integer arithmetic functions”, with the idea this header already contains functions that operate on integers. But, the suggested wording could easily be adapted to be placed in a different header (clause 7) or even to be introduced as proper operators (clause 6).

The choice for the name is guided by the fact that min and max identifiers are abundantly used in user code, not only for functions or macros that perform these operations, but also as variable names that store the respective minimum or maximum value in a given situation. The prefix stdc_, which is reserved for identifiers with external linkage, seemed the most natural one: sufficiently short and unambiguous.

3 Implementations

Implementations of minimum and maximum functions there are plenty. Even the current C standard provides a handful of slightly different interfaces for maximum functions, but only for floating types. Then, in the field an abundant number of implementations of varying properties and quality are added to that picture.

We are not aware of an implementation of these functionalities that resolves all the problems that are listed in the introduction. In particular, none of them seems to provide an integer constant expression where this would be possible. This is all the more surprising as that property has a clear demand (in particular for array lengths) and as providing macros that have that property is not too difficult by using either implementation-specific extensions (such as gcc’s __builtin_constant_p) and/or playing tricks with _Generic.

The goal of this paper is to propose a unification to all these interfaces and their behavior, such that programmers find reliable implementations of these features in their C library.

The reference implementation that is available on the WG14 git (and that we are able to provide on request) is not meant to suggest any particular way in which these features should be implemented, but only to prove that an implementation is possible as of today with relatively small effort. Besides a lot of C23-only features, this reference implementation uses only the __COUNTER__ pseudo macro (which is accepted for inclusion into C2Y).

We also provide an alternative implementation that uses compound expressions which could easily be replaced with lambdas or other forms of function literals. The advantage of that is that it avoids declaring a whole bunch of auxiliary inline functions. Its disadvantage is that this implementation with extended expressions currently doesn’t work for integer constant expressions in file scope. This is due to a restriction in gcc that forbids compound expressions in file scope, even if they are only present in a dead branch of a _Generic expression.

Our expectation is that compilers will provide buildins for these features and that the macros proposed here will then only serve as stubs to ensure standard conformance.

4 Suggested Wording

In 7.25, bump the value of __STDC_VERSION_STDLIB_H__.

Then add a new clause

7.25.7.3 Minimum and maximum operation type-generic macros

1 The following macros perform minimum and maximum operations. These operations are applicable to pairs of values of any integer type. In the following A and B denote the promoted type of the first and second argument of an invocation, respectively.

2 Synopsis

#include <stdlib.h>
minType stdc_min(A a, B b) [[unsequenced]];
maxType stdc_max(A a, B b) [[unsequenced]];

Description

3 After argument promotion, these type-generic macros compute the minimum and maximum value of their arguments. If both arguments are integer constant expressions, the macro invocation is an integer constant expressions, too.
4 The result type maxType is the common real type of the types A and B after the usual arithmetic conversions (6.3.2.8). The result type minType is determined as follows:

Returns

5 The result of the operation is the value of the mathematically lesser (respectively greater) argument converted to type minType (respectively maxType).

6 NOTE The types minType and maxType are able to represent the mathematical result of the corresponding operation.

7 Example
#include <stdlib.h>
constexpr size_t n = SIZE_MAX;
static double A[stdc_max(n, 1)];

constexpr auto max1 = stdc_max(n, 0);          // (size_t)n
constexpr auto min1 = stdc_min(n, 0);          // (signed int)0

constexpr auto max2 = stdc_max(n, 0u);         // (size_t)n
constexpr auto min2 = stdc_min(n, 0u);         // (size_t)0

constexpr auto max3 = stdc_max(n, (char)0);    // (size_t)n
constexpr auto min3 = stdc_min(n, (char)0);    // (size_t)0 or (signed int)0

constexpr auto max4 = stdc_max(3wb, 7wbu);     // (unsigned _BitInt(3))7
constexpr auto min4 = stdc_min(3wb, 7wbu);     // (signed _BitInt(3))3

constexpr auto max5 = stdc_max(-1wb, 255wbu);  // (unsigned _BitInt(8))255
constexpr auto min5 = stdc_min(-1wb, 255wbu);  // (signed _BitInt(2))-1

constexpr auto max6 = stdc_max(-1wbu, 255wbu); // (unsigned _BitInt(8))255
constexpr auto min6 = stdc_min(-1wbu, 255wbu); // (unsigned _BitInt(8))1
Here, the array length expression of A computes the maximum of two integer constant expressions and is thus an integer constant expression, too. For min1, one of the promoted arguments to stdc_min has a signed type, so the result type is that signed type. Conversely, for min2 both promoted arguments have an unsigned type, and so the result is the common real type. The type of min3 depends on whether or not char can hold negative values and of its width. If char has no negative values and the same width as int it promotes to unsigned int and thus the result type is size_t; otherwise the result is signed int. The type of max4 is the common real type of signed _BitInt(3) (for 3wb) and unsigned _BitInt(3) (for 7wbu) which is unsigned _BitInt(3). For min4 with the same arguments, the result type is signed type of the two, signed _BitInt(3). For min5, since one of the types is signed the result is that type signed _BitInt(2). For min6, -1wbu is the maximum value of unsigned _BitInt(1) which is 1; since both of the types are unsigned the result is converted to the common real type unsigned _BitInt(8).