
The joy of max() - thefox
https://lwn.net/Articles/750306/
======
tbodt
The most insane macro in that is this monstrosity:

    
    
      #define __is_constant(x) \
          (sizeof(int) == sizeof(*(1 ? ((void*)((long)(x) * 0l)) : (int*)1)))
    

If x is a compile-time constant, (void* )((long)(x) * 0l) is equivalent to
NULL, otherwise it's just a regular void* . And if both sides of a ternary are
pointers, the rule is:

If one side is NULL, the type of the ternary should be the type of the other
side (int* in this case).

Otherwise, if one side is a void* , the type of the ternary should be void* .

So if x is constant, the type of the ternary is int* , and if it's not
constant the type is void* . The sizeof(* ()) around it turns that into
sizeof(int) or sizeof(void), and in the GNU dialect sizeof(void) is 1, which
is different from sizeof(int). Whew.

What I want to know is what's wrong with __builtin_constant_p.

~~~
halayli
It would be nice to credit this explanation to Linus.

~~~
anothergoogler
Yea I mean isn't parent's comment just a ripoff of that entire thread?

------
netheril96
I often find it amusing how C advocates complain about the complexity of C++,
and then proceed to implement the same complex functionality in even more
brittle ways. Language features I have seen C developers emulate poorly in C:
constepxr (here), virtual functions (with function pointers), templates (with
macros), exceptions (with setjmp/longjmp).

~~~
static_noise
The only argument for C and against C++ is the slippery slope of wanting to
use one useful feature and ending up using a thousand features where noone is
really sure what's actually happening in the end. Then again they start re-
inventing "C with classes" using unsafe structs and macros. It seems that the
problem is not really with the language but the developers who cannot restrain
themselves.

~~~
astral303
In C++, you don’t pay for features you dont use. Linux kernel has virtual
calls for file systems via function pointers. That’s polymorphism. Why not let
the compiler handle that?

~~~
static_noise
You misunderstood on the meta level. My point was that you want to use one
feature of C++, you enable C++ to use this feature. Developer B wants to use
ten other features. Developers C..Z want to use different features. Every
feature by itself makes some sense in its context but in the end you're using
so many different C++ features that no single developer knows them all. If you
use modern C++ there are some ugly edge cases you have no idea they exist and
if you use older C++ styles there is hell waiting for you.

The hard part is to select which features of C++ to use and more importantly
which NOT to use and to ENFORCE these rules.

~~~
lomnakkus
> The hard part is to select which features of C++ to use and more importantly
> which NOT to use and to ENFORCE these rules.

Maybe I've just not kept up properly, but I find it surprising that there
isn't a tool which can enforce this already. It seems so obviously desirable.

I guess clang-tidy + custom "checks" \+ CI server enforcement could achieve
something similar, but I'm just surprised that it doesn't exist.

(Of course, many of the useful metaprogramming libraries for C++, e.g.
Boost::Hana, use a _lot_ of weird features of C++ to achieve their goal of
being simpler to use for clients of the library. I suppose it might be
possible to limit the warnings to "self-implemented" functionality, but it
might be tricky...)

------
rwmj
The lesson here is that if you're designing an operating system, you should
also be designing/evolving the programming language to go alongside it. The
original authors of Unix did this (developing C in parallel), and so have many
more obscure OSes. This could have been written simply as 'max()' with
appropriate modifications to the compiler to make it do the right thing.

~~~
brianon99
Things have changed over the years. Even if the developers have patched gcc,
or "own" gcc, linux still have to be built with gcc 4.4 otherwise many distro
will have problems, so it does not solve the problem.

Moreover, change the semantics of C as you have suggested will break other
non-linux-kernele existing programs. There may exists programs depending on
the K&R version of max with side effect. Who nows?

~~~
mjw1007
I think asking the GCC developers for a builtin form of __is_constant would be
a sensible step, so that the worst of those macros can eventually be retired.

------
RcouF1uZ4gsC
This is why C++ is so nice. Constexpr fits the bill perfectly instead of using
these non-portable hacks.

~~~
Animats
Look up the source code for "max" in C++. It's huge.

~~~
smitherfield
For Linux kernel purposes the following one-liner would suffice

    
    
        template<class T> constexpr T max(T l, T r) {return l < r ? r : l;}

~~~
ploxiln
A difference is that the linux macro works for non-constant expressions as
well.

All the crazyness is in order to support both constant expressions (and keep
them constant expressions) and non-constant expressions with the same max()
macro.

~~~
andrepd
That version does this. From cppreference (emphasis mine):

>The constexpr specifier declares that it is _possible_ to evaluate the value
of the function or variable at compile time. _Such variables and functions can
then be used where only compile time constant expressions are allowed
(provided that appropriate function arguments are given)_.

------
gshrikant
What exactly is the typecheck macro doing here? I get that it compares the
sizes of two pointer values but I don't know why and I feel like there is some
C standard nuance involved here that I don't understand. Also, is the `sizeof`
used to force evaluation at compile time?

~~~
tbodt
The type of == is always int, so the sizeof is sizeof(int), and the !! makes
the result always be 1 (true). The entire purpose of the macro is to have the
compiler warn if the types are incompatible.

~~~
gshrikant
Thanks! However, I still don't understand this completely. So the sizeof is
there just to force evaluation of the comparison and because == can only be
used among compatible types the compiler would warn? Why is the !! necessary
then wouldn't sizeof(int) be enough as a "true" value?

------
nothrabannosir
I’m surprised this is necessary at all. Wouldn’t a compiler at any reasonable
optimisation level optimise __cmp_once into __cmp automatically (and then into
the resulting max constant) if used with constants? Seems like very basic
constant propagation.

~~~
CJefferson
The problem is cmp and cmp_once are actually different if x and y are function
calls. In that case cmp calls the functions twice.

~~~
loeg
I think you've misread the question. Grandparent isn't asking why use cmp_once
at all; they're asking, why not always force the single evaluation of x and y
(i.e., always use cmp_once and let the compiler optimizer reduce the resulting
expression)? A responsive answer is given in
[https://news.ycombinator.com/item?id=16720118](https://news.ycombinator.com/item?id=16720118)
.

------
wruza
I would like to use a language that doesn’t involve sizeof(typeof...) and
template(Tmagic...) both.

    
    
      #include “meta.h”
    
      @tr max(@ta a, @tb b)
        if (is_comparable(ta, tb))
          tr = common_base(ta, tb, optionshere...)
          produce_code {...}
        else
          compile_error “incompatible types in $(__func__)”
    

I can’t figure out for decades why can’t we just get all ideas from lisp and
code in happiness.

~~~
devit
Lisp has hard to read syntax and no static type system, so not really
something to imitate.

~~~
wruza
I think I should stress out that my hope is not to use lisp instead of our
languages of choice, but to simplify metaprogramming tasks in them with _at
least_ equally simpler approaches that lisp has.

E.g. all C constructs can be described in a set of structs. IfStmt, CallStmt,
BinopExpr, etc. Generating or inspecting these on the fly could allow to
create hundreds of metaprogramming frameworks, few of which could prove their
best fit. But instead we locked in ugly cpp macros and C++ templates that even
seasoned haskell monader hardly understands and has no tools to explain.
Debugging is hard and manual, metadebugging is not even a thing, cppcheck and
other code analysis tools are enormously complex and rare, apple ARC is a
propietary language feature for NSObject instead of two pages of metacode
available to anyone with an “int rc” in their struct. I hope that snippet
above is now more clear.

------
AnssiH
For an explanation of __is_constant(), see the thread where it was suggested:
[https://lkml.org/lkml/2018/3/20/845](https://lkml.org/lkml/2018/3/20/845)
(this was also indirectly linked to in the article)

tl;dr: If one of the expressions in a ternary operator is a null pointer
constant, the result type is that of the other expression.

------
andrepd
This in cpp:

    
    
        template<class T> 
        constexpr const T& max(const T& a, const T& b) {
            return a>b ? a : b;
        }

~~~
loeg
The C version works with distinct types for a and b. Does this?

~~~
andrepd
This one works too, if they are implicitly convertible (e.g. max(1, 2.0))

~~~
bstamour
No it won't. You'll get a compile time error, as T could either deduce to int
or double. Unless you explicitly specify it as max<int>(1, 2.0) or
max<double>(1, 2.0).

------
krallja
I’m not up to date on GCC macros, so I’m not familiar with this syntax:

    
    
        #define __cmp_once(x, y, op) ({	\
            typeof(x) __x = (x);	\
            typeof(y) __y = (y);	\
            __cmp(__x, __y, op); })
    
    

Is that a block-expression, or what?

~~~
krallja
Ok, I found the doc for GCC Statement Expressions. Seems very Ruby-like.

[https://gcc.gnu.org/onlinedocs/gcc/Statement-
Exprs.html](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html)

------
jstanley
What's the advantage of ever using cmp over just always using cmp_once?

~~~
loeg
There's an answer in another thread:
[https://news.ycombinator.com/item?id=16720118](https://news.ycombinator.com/item?id=16720118)

~~~
jstanley
I still don't get it.

If it can't tell that the cmp_once is a constant expression why can it tell
that cmp is a constant expression?

EDIT: I think I get it. In the event that the arguments are constant,
something about the typeof business prevents it from being constant, so that's
no good if required in a context where a constant is required. But, when not
required to be a constant expression, you want to enforce single-evaluation of
the arguments, so then cmp_once is used.

~~~
loeg
I think it's the ({ }) bit (or perhaps the typeofs) that turns it from a
constant expression to a constant value, but I'm not really sure. But yeah,
something about cmp_once makes it the wrong kind of syntax.

------
drngdds
I'm really confused by the 1s in this macro. What are they, syntactically? The
second one looks like it could be being cast to the type "pointer to y" but
the first one has the sizeof expression in front of it.

    
    
       #define __typecheck(x, y) \
    		(!!(sizeof((typeof(x)*)1 == (typeof(y)*)1)))

~~~
ivanbakel
Both are being cast - the brackets are a little confusing, but the sizeof
expression is for the result of the comparison.

