The joy of max()
lwn.net
lwn.net
#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.
1. __builtin_constant_p might not return a constant--it might return a magic value that says "I don't know yet, ask me again at a later optimization step". __builtin_choose_expr needs to be evaluated pretty early, so "ask again later" isn't good enough for it.
https://lkml.org/lkml/2018/3/18/268
2. It doesn't really care if it is constant in the __builtin_constant_p-sense, it cares if it has side-effects.
> ({x++; 0;}) is constant in the eyes of __builtin_constant_p, but not side-effect free.
>That's not "an idea".
>That is either genius, or a seriously diseased mind.
>I can't quite tell which.
What needs to be happening, to avoid crashes and get correct functioning:
1) virtual method tables need to be allocated (otherwise you might be getting a function pointer from unallocated memory and calling it. Good luck surviving that. Bugs like this have happened in both kernel and GNOME).
1b) Pointers to the virtual method tables need to be correctly set upon every allocation of the struct that contains them. So you lose the ability to create an instance without constructing it. (again plenty of bugs)
2) every level of the inheritance hierarchy need to fill in the function pointers in the correct order (You can't count the number of bugs of this type in GNOME).
3) the pointers for same functions need to be at the same memory location for every object (and the corollary. Function pointers for different functions cannot, at any point of the inheritance tree, have the same location). This leads directly to the kernel datastructures, where everybody is utterly terrified of changing anything or even reordering fields. And sadly, that fear is there for good reason.
4) 1, 2 and 3 need to be redone (best from scratch) any time the inheritance hierarchy changes. And of course, need to be done correctly, so you really ought to erase the whole thing in the whole inheritance (and somehow tell out-of-tree developers to do the changes on their end), but in C nobody does this because of the amount of work involved. Then ... bugs happen.
5) May God help you if you modprobe a module across one of these changes without recompiling it. In other words, any change to the inheritance hierarchy risks making out-of-tree modules deathtraps (such changes require modifying the source to the out-of-tree module and recompiling)
In C++
a) put "virtual" in front of the function you want to work across the inheritance hierarchy
b) (optional) don't remove or reorder virtual functions in versions where you want to maintain binary compatibility (caveat: not in the functions themselves, and of course, not in any data structures they might use) (in short: anything out-of-tree needs to be recompiled)
Given that KDE maintains binary compatibility across major versions b) is not optional within that project (with a bit of a cleanup at avery major version). Except for DCOP. But if you recompile from scratch for every deployment like every large C++ shop, you can just outright ignore it.
The result of this is that the lookup tables can be compiled into programs. This works ridiculously fast. A virtual function call in C++ is one indirection. Not even one extra instruction (just a much more expensive one than the one you'd ordinarily use).
In Java, if I remember the last time I checked it was ~20.000 instructions (but can be compiled out by the JIT compiler ... eventually. Then it's still ~1000). And I assure you Java is a lot more efficient at this than any scripting language.
This is the usual difference between C and C++. In C++ you get all the advantages that lots of manual work gets you in C, at the same runtime cost.
The criticism is that if you give inexperienced programmers lots of high level tools, they will quickly use it to blindly generate programs that are 20G+ of machine code. And ... well that's true. Fixing it can be pretty hard.
The arguments of people like Linus are essentially that it's a good thing that people go through and redo the low-level stuff regularly. It's a lot of work but at times you find problems and inefficiencies. I do agree with that, but sadly, I find it pretty hard to assemble a 2K+ member team that I don't have to pay. So work that programmers don't have to do is a win for me.
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...)
That’s pretty much all of us IMO. We need to get stuff done, and if there’s a feature available that lets us do it quicklier, we use it. We take the path of least resistance—a warning saying “don’t” provides no actual resistance.
That’s why I like languages like Haskell that by default prevent me from doing anything I’m probably going to regret later; if I want to break referential transparency, type safety, memory safety, &c. then I can always opt in explicitly and grep for it later when it hits the fan.
That said, I do prefer C over C++—really, the only things I miss are destructors (local automatic memory management is nice) and templates (generic/type-level programming is nice), but they’re not critical for the kinds of stuff I use C for anyway.
head []
(Not that I disagree with what you want, just that even Haskell has made some mistakes :-P) ∀a. [a] → a
Viewed as a logical formula, it’s not a tautology, so I know it must be partial. Proof: Given: L(a) = μr. 1 + (a × r)
∀a. L(a) → a
= [definition of L]
∀a. (μr. 1 + (a × r)) → a
= [unroll μ]
∀a. (1 + (a × μr. 1 + (a × r))) → a
= [∀xyz. (x + y) → z ⇔ (x → z) × (y → z)]
∀a. (1 → a) × ((a × μr. 1 + (a × r)) → a)
= [(∀a. 1 → a) = 0]
∀a. 0 × ((a × μr. 1 + (a × r)) → a)
= [∀x. 0 × x = 0]
∀a. 0
=
0
Unfortunately, this is also false if a is false (void/bottom), and all those “→ a”s are actually “→ ¬¬a” because Haskell isn’t a total language—with that pesky intuitionistic double-negative, any function can be partial. But it’s a good rule of thumb for quickly checking whether a function must not return for some (or all) inputs.But C function pointers are not really emulating virtual functions. They're better and more powerful than virtual functions.
"Better" is a value judgment, so I'm not sure about that, but...
AFAICT they're more like prototypal inheritance than the inheritance model of C++.
How would do I do this in C ?
int some_function() {
// some processing
fp = package_the_rest_of_this_function_into_a_function_pointer()
do_some_background_processing_and_callback(fp);
// all the background processing is done
}
In LISP, this is easy, in fact, in most LISPs, it's builtin.Can you give examples of this "much much more" you're referring to?
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?
IDE support, debugging, instrumentation, profiling, portability, community libraries.
> It sucks. Trust me - writing kernel code in C++ is a BLOODY STUPID IDEA.
C++ has evolved tremendously since. There are better arguments.
(and I say this as a C developer who doesn't believe C++ is the right answer)
(not that I'm arguing for C++ in linux or such)
Recently created a third party SDK DLL in c++ and could not pass c++ types natively (unless you coupled to compiler and maybe version of compiler).
The important part is that it is used by most, if not all, C++ compilers on that platform.
UWP now even supports generics and actual inheritance.
* C++ has advanced considerably, and features like constexpr, lambas, unique_ptr, etc make writing code easier without runtime overhead.
* The C Compiler that Linux uses, is now written in C++
* Clang tidy checks make it relatively easy to keep problematic C++ features out.
The fact that C++ is largely a superset of C means that just like GCC did, there can be a gradual migration of code.
...and gained considerable complexity in the process. (I'm aware that C has gained some too, but not to the extent of C++.)
This is not the case for evolving C++.
And yes, the fact that abi stability isn't really a thing in c++ land is a huge problem too. It has been tried before with beos and it sucked.
template<typename _Tp>
_GLIBCXX14_CONSTEXPR
inline const _Tp&
max(const _Tp& __a, const _Tp& __b)
{
// concept requirements
__glibcxx_function_requires(_LessThanComparableConcept<_Tp>)
//return __a < __b ? __b : __a;
if (__a < __b)
return __b;
return __a;
}
I mean, there are more overloads, but it isn't what I'd call huge.To be honest, I'm just a bystander here but I did actually find the C based max() presented easier to appreciate than the C++ one you present here.
The C one looks more like a maths proof: build the argument ... et voila. It also does not have comments inline because it is a sort of comment. The C++ version has two inline comments, one of which is incorrectly formatted (space or no space after indicator) and the second one seems superfluous because it uses code to describe code. The C++ version, after you strip out the comments, looks very concise. If it does the same job then good stuff. After that we are down to whether x,y is a better choice than a,b. On balance I prefer a,b for arbitrary inputs and x,y etc for functional dependent - so the C++ example wins here for me.
This seems like the kind of thing which is easily corrected and probably not relevant for determining which is the better approach.
That chunk of code is one of the fundamental building blocks of C++. When presented as exhibit A it should look good. To an outsider that sort of thing looks bad, even if the presented thing is correct the silly error will be obsessed over - as here.
If something is important enough, and I think that C++ is one of those, then for $DEITYS sake fix the obvious stuff before having to be an apologist for it. A simple sed script run decades ago would have done that.
The reason I am labouring this point is that one day it wont be a discussion on HN debating this sort of bollocks but something involving lots of dosh. If S&M ever realised what source code really looked like - they'd have a fit. The world turns ...
It makes sense to drop the space before the start of the commented code because that way you can just delete the // and obtain the original source line.
}
int foo(){
for(x=1; x<len*2;x++){//over 2 array len
...(no empty lines for next hour)
I know I’m clinical perfectionist, but try to see it behind my complaint. Our brain can process only few things in parallel. If you overload it with parsing and do not provide hints like grouping and/or formatting, you occupy a couple of threads for a complete bs. As if programming was not one of the hardest things already.In practice, the style for writing STL code is often quite ugly (sometimes deliberately ugly), for reasons which aren't really interesting to most people.
It’s not unusual to use a space after // for comments and no space for code that has been commented out.
...
#define __is_constant(x) \
(sizeof(int) == sizeof(*(1 ? ((void*)((long)(x) * 0l)) : (int*)1)))
...
How many C programmers would even understand what that code does, without an explanation? template<class T> constexpr T max(T l, T r) {return l < r ? r : l;}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.
>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).
T max(T)(T a, T b) { return a < b ? b : a; }
without requiring explicit constexpr annotations and with less noisy template syntax ;).Or what edflsafoiewq said: https://news.ycombinator.com/item?id=16721559
It is however trivial to write a version that works with distinct types, if that were really what you want.
template<class L, class R>
constexpr std::common_type_t<L, R> max(L l, R r) {return l < r ? r : l;}This is a property of c++ implicit constructor rules, it is not unique to this function. In most cases this is something you want to avoid but for integer promotion it can some times be useful.
int* x = malloc(5 * sizeof (int));
because in C++ you cannot implicitly cast a void pointer. max<int>(-1, 2u) // => 2It doesn't "do" anything otherwise; it always evaluates to "1".
#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.The first is subjective, and there are Lisp-family languages with static typing.
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.
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.
template<class T>
constexpr const T& max(const T& a, const T& b) {
return a>b ? a : b;
} template<typename T>
inline constexpr const T& max(const T& a, const T& b) {
return (b < a ? a : b);
} #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? foo() > bar() ? foo() : bar()
can have a different result than int __x = foo();
int __y = bar();
__x > __y ? __x : __y int things[max(size1, size2)];
and there's been recently a push to remove all VLAs (arrays with non constant size) from the kernel. The -Wvla flag was warning about these arrays even though they're actually constant size, so something needed to be done.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.
#define __typecheck(x, y) \
(!!(sizeof((typeof(x)*)1 == (typeof(y)*)1)))