Also, while I can reasonably beliveve that most of the overhead goes away at -O2 or -O3, this has to just trash performance for debug builds, which is not unimportant.
Also, while I can reasonably beliveve that most of the overhead goes away at -O2 or -O3, this has to just trash performance for debug builds, which is not unimportant.
To me the implementation seems to be a good solution given that you don't want to change the core language.
Fix the damn language.
Welcome to engineering tradeoffs.
For run-time performance, yes, you definitely get a hit in your run time if you have all optimizations off. However, I have found that the practical benefits of things like this library, combined with run-time sanitizers, is worth much more at finding bugs than a debugger, so I typically debug and develop with `-O3 -fsanitize=undefined -fsanitize=address` and release mode is `-O3 -flto=thin -DNDEBUG`. This is an area where it's not possible (yet) to satisfy all use cases. I am hopeful that in the future, it will be much easier to tell your compiler "this is the code I care about debugging, optimize the rest".
I found that it didn't really hurt compile times much (500KLOC compiles in about 40 seconds with make -j on a 16-core box), and performance is just barely impacted at -O2, but it definitely destroys debug build performance completely and makes a mess of code profiling and backtrace debugging output.
There are also all kinds of subtle gotchas: you can make "x ? y : z" work when y and z are different primitive types, but not if one is a template. You cannot make them work with printf("%d", x) directly (it compiles fine and then you run into problems), etc.
Trying to enforce stronger type checking (not allowing the implicit mixing of signed and unsigned, etc) introduces endless worlds of pain and ambiguous operator overload problems.
Trying to make eg "x++" or "x+y" return another template instance instead of a primitive type similarly causes a world of pain everywhere.
I still feel it was 100% worth it in my case, but it has many pitfalls. Taking it even further to bounding to specific ranges as this library aims to do feels even more fraught with danger. I do not envy the work of its authors.
To be fair that's also true for standard intN_t or uintN_t types. "%d" expects an int argument, and while several of the intN_t types may be converted to int by the default argument type promotion rules, that isn't guaranteed. For example, while plain "%d" works for int64_t on ILP64 platforms with 64-bit int values, LP64 (32-bit int, 64-bit long) requires "%ld" and 32-bit platforms require "%lld". That's why you have e.g. the PRId64 macro from <inttypes.h> to select the correct format code for printing int64_t values. You could provide something similar to abstract away the required format code. If you're using the GNU C library you can even define custom format codes with register_printf_function(), which might be your only option if the calling convention for your template doesn't match any of the standard integer types.
In fact, the README says as much: it has only zero time/space overhead "assuming basic compiler optimizations like inlining".
And I did actually look at the code, but at a glance I couldn't get much sense of it. The main header imports like two dozen other headers, and the "detail" folder is filled to the brim with more headers. I tried cloning and using it, but I couldn't get it to compile (which is probably mostly my fault, but I really didn't want to spend too much time on it). I did run just the preprocessor though, and "#include <bounded/integer.hpp>" expanded to 48000 lines. 48000 extra lines to compile in order to use integers. For every file you use it in.
I don't want to be too harsh here, I'm sure it's an excellent library and it does what it says brilliantly, and if you need bounded integers I'm sure it's awesome. But language like "[integers in C++] are mostly unusable" rankles a bit, and the the implication in this file is that this is something you should regularly use instead of integer types.
I personally feel that libraries like this are taking C++ in the wrong direction ("ranges" is another obvious example) and making the language less and less usable in my profession.
I mean, before I wrote my comment I checked and it's `constexpr` all the way down to the add instruction so if it's going to make a call, I'm not seeing it. There is definitely a lot of template machinery, but I can't be the judge of that immediately.
> I personally feel that libraries like this are taking C++ in the wrong direction ("ranges" is another obvious example)
I think you're making an emotional argument based on some preconceived twitter-verse sentiment. I'm in games too (graphics specifically) and people are up and arms about the wrong thing. Maybe there are corner cases that are a bit overly complex, but what's wrong with being able to write `std::sort(container)` instead of `std::sort(container.begin, container.end)`. There may be things we don't like, but I don't think we should resort to hyperbole either.
Incidentally, I wouldn't use this library if only because for what I do, having explicitly sized data types is important.
Illustration, compare the assembly for "foo1" and "foo2": https://godbolt.org/z/t9Zkx-
That's a variation of "did you even read the article", which the HN guidelines specifically ask users not to post, so please edit those out of your posts. The comment would be just fine without that bit.
"Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to--they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980, language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
-- Tony Hoare, "The 1980 ACM Turing Award Lecture"
Also, what do you mean by "this has to just trash performance for debug builds". Do you mean runtime performance?
In more explicit terms-- you are a soft-realtime app developer trying to live in a world that rarely measures performance or even writes specs with your constraints in mind.
I wish you game devs would harp on this more. I'm just a lowly soft-realtime audio software dev. My voice doesn't carry because there aren't mountains of cash behind me to help echo it.
I've worked on games with "DebugOptimized" builds where it's optimized,but instrumented in other ways. That's probably the best solution. You only drop down to full debug if you absolutely can't debug a specific issue in an optimized build.
With Visual C++, if you are careful you can build a version with release runtimes and third party libs so only your code is debug. Microsoft makes it harder than it has to be though.
But yeah, very often it’s basically the only option, running an optimized build with debug symbols. This is why it’s such a problem with C++: debug builds are frequently pointless because the performance is so bad, which makes debugging a lot harder.
Over in Linux/g++ land, it's not nearly so bad. Bounds errors are mostly handled by address sanitizer such that -O0 performance is decent. And if -O0 is too slow, there's -Og which takes slightly longer to compile and has less information for gdb.
I work almost exclusively in "g++ -O0" until it's time to release.
Clang is decent too, but I find the C++ compile times are atrocious, like almost double for my admittedly template-heavy code. And performance of the compiled output is no better.