From owner-sc22wg14+sc22wg14-domo2=www.open-std.org@open-std.org  Wed Mar 11 23:14:04 2026
Return-Path: <owner-sc22wg14+sc22wg14-domo2=www.open-std.org@open-std.org>
X-Original-To: sc22wg14-domo2
Delivered-To: sc22wg14-domo2@www.open-std.org
Received: by www.open-std.org (Postfix, from userid 521)
	id 0D2F5356D61; Wed, 11 Mar 2026 23:14:03 +0100 (CET)
Delivered-To: sc22wg14@open-std.org
Received: from mail-dy1-f174.google.com (mail-dy1-f174.google.com [74.125.82.174])
	by www.open-std.org (Postfix) with ESMTP id 6CDB3356AA9
	for <sc22wg14@open-std.org>; Wed, 11 Mar 2026 23:14:02 +0100 (CET)
Received: by mail-dy1-f174.google.com with SMTP id 5a478bee46e88-2b4520f6b32so704014eec.0
        for <sc22wg14@open-std.org>; Wed, 11 Mar 2026 15:14:02 -0700 (PDT)
ARC-Seal: i=1; a=rsa-sha256; t=1773267241; cv=none;
        d=google.com; s=arc-20240605;
        b=ZAkRBvZpbyQCGKVMR/G3+I2rab2nfPoOWSLLGkNY6JzrEBaolJZsvISTd5c5uMpVj5
         /VHgt37v5WXYEuA3GT2Z6CGw3RFWLRahz3Tr60WaYWaMbZjZ+B+mzIMEBXU27fG9Cjd8
         bw36eoio/DS+X2z6oq1y5Z1mxzpdDSXqvw4vvHeKiAt9hSp6rOpvrQcqndrLr7lcwMWD
         gbYJcPaYPZF7aoB4M0QVKe7ZqU1DBPfoxeZmpciv3RgpfTV0bDW/SwcIazXYntYgUE3n
         /p1yI9Pja3Hr/rNtdpt9EEEjoWWyITcx/rl6HY5udlkdSmPm9wwS0NwlglJV9c1jc+s9
         AXAg==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605;
        h=to:subject:message-id:date:from:mime-version:dkim-signature;
        bh=rYTB716WqqCcJ1BcxWckN4ACXl9lepR6e1cwPH6cn9E=;
        fh=ArEjJguew1GBOj/hflxzJsqbN9INf8uHQLf+eHp70sw=;
        b=iKnCCJQbtFxKZKSI/AdfwZbj/96zGSHY1acn3aQJ8IrTDb/ulPCLSBSbUTpCkBKsd3
         NEhr5v5ToyPaVL/w2XrISthIbVMhxwhsokgHO//LpIAmLprqf7h6EEn52XVFmwLxYbog
         h9pnjftjUF1pgdE6c87IO/WvxEf7nbiiZJJn1IM1PsUNSj3MBwkCAdpJGZiKVDtRCoAZ
         kv0iUBlRhH3eW9Ji9RCLBGxUZKo7FL82u2jQ8fppFkODd5U4D4O0Fh+/TN0JKUC4yDcD
         jReLgbiZDAWfOYe2PP73u2yA3uUptOqxyQHjxgQN3aDkPt1a1sGqG7t1VYqiQ35RFCpT
         oD5Q==;
        darn=open-std.org
ARC-Authentication-Results: i=1; mx.google.com; arc=none
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=gmail.com; s=20230601; t=1773267241; x=1773872041; darn=open-std.org;
        h=to:subject:message-id:date:from:mime-version:from:to:cc:subject
         :date:message-id:reply-to;
        bh=rYTB716WqqCcJ1BcxWckN4ACXl9lepR6e1cwPH6cn9E=;
        b=B80BvlwLpOLNxuuprbKr0stWdqFZRNWDRbfuju94CpVkoH2lL7YhtPC+OC91eYcxu6
         GMUOA9K983ez+C2sA2jmTu8V0e4XNucKpxePiOfAz3AaMMEtBOpDVeV0TO8j896Rzlp1
         3zf1mhFGfHeXqRYGfK/5REn9wykSI9ODsvb4hEL5D0g32FlhqJU3KwVNs8Q5ILO+SINu
         E295jynhva6bpvhZAQqsTrZJZ5jx0Y/3v5JQx5CRg4AiUTZcAsk3Z9qcW+GIWCU+25YH
         qfQZII/HK6SHCWYsIktk1Fp7iurQL0u6bNsytMic+W6XU1ZEYtdtdCdzd5lJ4W+I5ZSC
         QHzQ==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20230601; t=1773267241; x=1773872041;
        h=to:subject:message-id:date:from:mime-version:x-gm-gg
         :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;
        bh=rYTB716WqqCcJ1BcxWckN4ACXl9lepR6e1cwPH6cn9E=;
        b=nDgyVmVDngGNHOaYJLxXB3r+A2FvAMo4XmaQFhZvPSVp2u/Gv+LOHhHw+NVV6d59F1
         Kf5yqkN9/OpzfnalqtGU3jlVbXki9/yH/DyyQsgnlGKFHb3pX2bKco0dJyvGidjMa+qG
         R3/0DRwQ8A+O2nvGJlqjum7/M4OBAA6/fhjRnObfmd9EppHfV1mRbx/P0F5FwdreTHln
         4nq72kpxZczDoQ4vIDs4ZNOTu7f+kvloXqB2kJNjWsCX65DdRYuvZJqoo4xrYjsrQhES
         8meLatyFuCH5I4EMqJ+wOK0PjKnRVVHYL/rOXToeIznp9nUP1b81HxZOEC1zhlP0oSf+
         xykw==
X-Gm-Message-State: AOJu0Yz5t9PN5pKsFIsReMdsqnsp5ARwFIuqvnsg9CvssW+nyh39QEEq
	Au2ZEJXiiQuYHYR+MDOldy+E+XwW5ovhZYaEcEyTWzyqi9l2mxlCUBMIUP3gLRHaQj6NgBxe0KU
	yQZrU/AThj9o8B04Hi35dbIY4GgvOhTnW+p02
X-Gm-Gg: ATEYQzxa2IS2dayB183J1GUI4K91Zg64TBJXBvGX5HEEr1JY8jrAVve6SXmlbVi13tr
	U7MevsOkV7wpnsFwuv84v6q6qgQxVpDdr/X5hlLHu/WAwh5qxbjcozYetM9FuQ5uM4wAz1Czhi6
	cjprHcJfKCEElltKWX8TtCL0nfLfsyH2i4FVgupkcDkwVTslDM/cJYpYIOAwQTURAsFYlkfG4UJ
	mgQ+aXRFTMLgTzX2xE3eRw7QUjktQLYfcT07X7bPqDt4WFkp2BMCfM8sVYqQcCJRH0FTaBlrhVm
	6xT4BjP5EynTGOSLm3RL96fv2I4om7j2tprISQM3qA3DZOGPl4AA9lrmQ0IzZ+2Y3g==
X-Received: by 2002:a05:693c:2b0c:b0:2ba:a465:3a20 with SMTP id
 5a478bee46e88-2be8a2e2edcmr1657885eec.24.1773267241010; Wed, 11 Mar 2026
 15:14:01 -0700 (PDT)
MIME-Version: 1.0
From: Christopher Bazley <chris.bazley.wg14@gmail.com>
Date: Wed, 11 Mar 2026 22:13:48 +0000
X-Gm-Features: AaiRm50GRjsoOd0vr1DgiGfs7jMNUdMRuD5ZUUMVZxw4-P8wOYklfFvtBXPeTr0
Message-ID: <CAEHU8x97Qp3dR3+ad64RQ1uredCw0K7Ze75hhSRxwdfH43yuHw@mail.gmail.com>
Subject: N3762 Add type-safe minimum and maximum type-generic macros
To: wg14 <sc22wg14@open-std.org>
Content-Type: text/plain; charset="UTF-8"
Sender: owner-sc22wg14@open-std.org
Precedence: bulk

Dear all,

I have created the following test program to investigate how the
behaviour of the stdc_min macro proposed by N3762 differs from two
other implementations:
 - a classic-style simple macro definition similar to that given in
The C Programming Language (1988, K&R).
- a simple implementation of the same thing using C11 _Generic to
avoid multiple evaluation.

I took the implementation of stdc_min from
https://gustedt.gitlabpages.inria.fr/ellipsis/ellipsis-minmax_8h_source.xhtml#l00155
because I believe it is intended to have the same behaviour.

Some things I consider noteworthy:

- stdc_min often produces a different answer from the macros whose
usage it is most likely to be used to replace.
- Even when stdc_min produces the same answer as the two simpler macro
definitions, it often produces a different type. I think most
programmers would be quite surprised by the fact that the type of
_STDC_min(1u,1) is 'int' rather than 'unsigned int'.
- Unlike C's usual arithmetic conversion rules, stdc_min does not have
platform-dependent behaviour. (Compare the results of KRMIN(1l,1u) for
MinGW gcc 15.2.0 and ARM64 GCC trunk, for example.)

If you consider existing practice deficient then presumably none of
this is concerning. I think providing macros that do something
different from most existing implementations of the same functionality
is a bit risky, even if the behaviour of these new macros is arguably
better designed. It prevents the standard macros being used as a
drop-in replacement.

I'd rather leave the diagnosis of mixed-signedness arithmetic to the
compiler, which seems to be what the designer of _Generic intended.

https://godbolt.org/z/vx53bjYjG

#include <stdio.h>

#define DEF_MIN(N, T) \
static T N ## min(T x, T y) { return x < y ? x : y; }

DEF_MIN(ll, long long)
DEF_MIN(l, long)
DEF_MIN(i, int)
DEF_MIN(u, unsigned)
DEF_MIN(ul, unsigned long)
DEF_MIN(ull, unsigned long long)

// My macro, created for this demo
#define CBMIN(X, Y) \
  _Generic((X) + (Y), \
    long long: llmin((X), (Y)), \
    long: lmin((X), (Y)), \
    int: imin((X), (Y)), \
    unsigned: umin((X), (Y)), \
    unsigned long: ulmin((X), (Y)), \
    unsigned long long: ullmin((X), (Y)))

// Check if promoted X has a signed type.
#define _STDC_signed_mm(X)                                      \
  ((typeof(+(X)))-1 < 0)

// From https://gustedt.gitlabpages.inria.fr/ellipsis/ellipsis-minmax_8h_source.xhtml#l00155
#define _STDC_mintype(X, Y)                             \
  _Generic((char(*)[1 /* never zero */                  \
                    | (_STDC_signed_mm(X)<<1)           \
                    | (_STDC_signed_mm(Y)<<2)           \
                    | (_STDC_signed_mm((X)+(Y))<<3)]    \
            )nullptr,                                   \
           char(*)[3]: +(X),                            \
           char(*)[5]: (typeof(+(Y)))(X),               \
           default: (typeof((X)+(Y)))(X))

// X converted to the type for the max feature.
#define _STDC_maxtype(X, Y) ((typeof((X)+(Y)))(X))

#define _STDC_min(X, Y)                                                 \
  (((((X) <  0) != ((Y) <  0))                                          \
    ? ((X) <  0)                                                        \
    : (_STDC_maxtype(X, Y) < _STDC_maxtype(Y, X)))                      \
   ? _STDC_mintype(X, Y)                                                \
   : _STDC_mintype(Y, X))

// Classic-style macro
#define KRMIN(A, B) ((A) < (B) ? (A) : (B))

#define PTYPE(T) \
  _Generic((T), \
    long long: "long long", \
    long: "long", \
    int: "int", \
    unsigned: "unsigned", \
    unsigned long: "unsigned long", \
    unsigned long long: "unsigned long long")

#define TEST(A, B) printf("KRMIN("#A "," #B ") = 0x%llx, type is %s.\n" \
                          "CBMIN("#A "," #B ") = 0x%llx, type is %s.\n" \
                          "_STDC_min("#A "," #B ") = 0x%llx, type is %s.\n\n", \
                          (unsigned long long)KRMIN(A, B), PTYPE(KRMIN(A, B)), \
                          (unsigned long long)CBMIN(A, B), PTYPE(CBMIN(A, B)), \
                          (unsigned long long)_STDC_min(A, B),
PTYPE(_STDC_min(A, B)))

int main(void)
{
    // n3762: Otherwise, minType is the same as maxType.

    /* C2Y: Otherwise, if both operands have signed integer types or
both have unsigned integer
       types, the operand with the type of lesser integer conversion rank is
       converted to the type of the operand with greater rank. */
    TEST(1, 2); // int
    TEST(2, 1); // int
    TEST(-2, -1); // int
    TEST(-1, -2); // int
    TEST(1, -1l); // long
    TEST(1, -1ll); // long long
    TEST(1, 1l); // long
    TEST(1, 1ll); // long long
    TEST((unsigned short)1, -1); // int
    TEST((unsigned char)1, -1); // int
    TEST(-1, (unsigned short)1); // int
    TEST(-1, (unsigned char)1); // int

    TEST(1u, 1ul); // unsigned long
    TEST(1u, 1ull); // unsigned long long

    // n3762: If one of the types A or B is signed and the other is
unsigned, minType is the signed type.

    /* C2Y: Otherwise, if the operand that has unsigned integer type
has rank greater
      or equal to the rank of the type of the other operand, then the operand
      with signed integer type is converted to the type of the operand with
      unsigned integer type. */
    TEST(1u, 1); // unsigned or int ****** DIFFERENT FROM C ******
    TEST(1, 1u); // unsigned or int ****** DIFFERENT FROM C ******
    TEST(1u, 2); // unsigned or int ****** DIFFERENT FROM C ******
    TEST(2, 1u); // unsigned or int ****** DIFFERENT FROM C ******
    TEST(1u, -1); // unsigned or int ****** DIFFERENT FROM C ******
    TEST(1ul, -1); // unsigned long or int ****** DIFFERENT FROM C ******
    TEST(1ull, -1); // unsigned long long or int ****** DIFFERENT FROM C ******
    TEST(-1, 1u); // unsigned or int ****** DIFFERENT FROM C ******
    TEST(-1, 1ul); // unsigned long or int ****** DIFFERENT FROM C ******
    TEST(-1, 1ull); // unsigned long long or int ****** DIFFERENT FROM C ******

    /* C2Y: Otherwise, if the type of the operand with signed integer
type can represent all
       the values of the type of the operand with unsigned integer
type, then the
       operand with unsigned integer type is converted to the type of
the operand
       with signed integer type. (THIS RULE IS PLATFORM DEPENDENT.) */
    TEST(1l, 1u); // long or unsigned long ****** DIFFERENT FROM C ******
    TEST(1ll, 1u); // long long

    /* C2Y: Otherwise, both operands are converted to the unsigned
integer type corresponding
       to the type of the operand with signed integer type. (THIS RULE
IS PLATFORM DEPENDENT.) */
    TEST(1ll, 1ul); // unsigned long long or long long ******
DIFFERENT FROM C ******

    return 0;
}

Christopher
