Add type-safe minimum and maximum type-generic macros

Jens Gustedt, INRIA and ICube, France

2026-04-01

target

integration into IS ISO/IEC 9899:202y

document history

C document ID: C4172

document number date comment
n3543 202504 original proposal
n3707 202509 remove floating types
place in <stdlib.h>
make the interfaces unsequenced
n3762 202512 revise wording for promotions
use unsigned long in the example instead of size_t
n3868 202604 remove the claim for ICE
revise wording for the type choice
move to <stdint.h>

polls

1 Introduction

Generic features to compute minimum and maximum value of two given integer values is notoriously challenging in C. The difficulties with such features are as follows

Previous revisions of this paper also discussed versions of these macros that would result in integer constant expressions if the arguments are so. This aspect did not find as wide approval as the feature in general, so we drop this idea for now. In can be added later without problems.

2 Approach

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

In particular, if one type is signed and the other is unsigned, we have to ensure that the result type can hold the result and that no sign conversion occurs when converting to the result type.

The proposed features have to be implemented side effect free (7.14) 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.

After discussion in WG14, we propose to add the features to the <stdint.h> header in a subclause of their own.

The choice for the macro names is guided by the fact that the 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 already reserved for identifiers with external linkage, seemed the most natural one: sufficiently short and unambiguous.

3 Implementations

Implementations of minimum and maximum macros or functions there are plenty. Even the current C standard provides a handful of slightly different interfaces for maximum macros and 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.

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).

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.23, bump the value of __STDC_VERSION_STDINT_H__.

Then add a new clause

7.23.7 Minimum and maximum type-generic macros on integer values

1 These macros perform the minimum and maximum operations of two integers. They are applicable to pairs of values of any integer type and return the numerically exact result of the operation. In the following A and B denote the type of the first and second argument of an invocation after performing integer promotions.

2 Synopsis

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

Description

3 These type-generic macros apply integer promotions to their arguments and then compute the minimum and maximum value of the results.

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).

5 If A and B are both signed types or both unsigned types, the result type minType is their common real type after the usual arithmetic conversions (6.3.2.8). Otherwise, minType is the signed type of the two.

Returns

6 The result of the operation is the value of the numerically lesser (respectively greater) promoted argument converted to type minType or maxType, respectively.

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

8 Example

#include <stdint.h>
unsigned long n = ULONG_MAX;

auto max1 = stdc_max(n, 0);          // (unsigned long)n
auto min1 = stdc_min(n, 0);          // (signed int)0

auto max2 = stdc_max(n, 0u);         // (unsigned long)n
auto min2 = stdc_min(n, 0u);         // (unsigned long)0

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

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

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

auto max6 = stdc_max(-1wbu, 255wbu); // (unsigned _BitInt(8))255
auto min6 = stdc_min(-1wbu, 255wbu); // (unsigned _BitInt(8))1

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 unsigned long; otherwise the result type 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).

Acknowledgment

I’d like to thank Aaron Ballman, Christoph Grüninger, Joseph Myers, Philipp Klaus Krause, and Robert Seacord for their suggestions.