C vs C++, from the author of ZeroMQ
-
Now I'm not a C/C++ programmer, but won't that do a null dereference if you pass it the start or end of the person list?
Normally you'd use a circular list either with or without head. Back when I programmed in pure C, clone of Linux list_head was my preferred tool. Of course, now whenever
std::vector<something>
does not work for me and something like that seems suitable, I just reach for boost::intrusive::list, which is exactly the same thing in a typesafe package.1) You can use a circular linked list with a distinguished "end" node and avoid the special cases. (Actually... I'm not quite sure why this isn't more common. Maybe it is and I just don't do enough C to see it, and always see abstractions.)
It's not a “distinguished end”, it's either head (if the list has a head) or it's the first element again.
catching the exception locally if you need to do some local cleanup
Actually, when you need to do some local cleanup, you should usually just create a guard that will do it in it's destructor.
His use of exceptions seem like he missed the point of what exceptions are good for.
There is a trouble with exceptions. Either you have to be serious about them, or leave them alone, but there is mess in the middle.
When you decide to use exceptions, you have to write the code to be exception-safe, that is that if exception is thrown through it, it will leave everything in defined state. You'll find yourself using a lot of RAII and
std::swap
ping from temporaries and hiding stuff in pImpls that can be swapped like that. It can be done, but it is a big change in programming style.Of course writing exception-safe code has a huge advantage, because such code is also resilient against resource leaks caused by simply forgetting to clean up, so you really should write it anyway. It goes without saying that this has no equivalent in C.
it's quite a lot harder to make a stable ABI with C++ than with C
Well, yes, I hate that too. Basically in C++ you have to specifically write a façade to hide the actual code behind. And templates (that are otherwise the too you use C++ for) can't be hidden, so when you use them, they are set in stone. Fortunately this problem only matters for system libraries and frameworks.
In a sensible language you'd also have a finally block to simplify matters, but in this case you can probably set checking to false in the catch clause just fine.
That's what the guards are for. Basically you write, once for all similar cases (or get somewhere), something like
template <typename T> class Restore { T old_value; T &variable; public: Restore(T &var, T val) : old_value(var), variable(var) { var = value; } ~Restore() { variable = old_value; } };
and then you simply
Restore<bool> rc(check, true);
(it can be improved to infer the type, with a bit of magic even in C++03) instead of
check = true
. And now it will be restored after exception, but you now also can't forget to restore it.C++ was the best of the worst before, but Bjarne still botched it by insisting on backwards compatibility with C - it exposed way too many naughty bare-metal bits for people to get hurt on.
Rust is the first language that can do most of the cool things I use in C++ and brings something useful as a bonus. D had the features, but not that much new stuff, but it failed due to disagreement about how standard library should look. Java and C# don't come even close. That does not mean Java or C# would not be better tools for many jobs. Hell, for GUI I'll take Python or JavaScript over C++ any time.
But most importantly, neither Java nor C# are portable between mobile platforms, so if you want that, you are still stuck with C++ — unless javascript is good enough for you, which, honestly, for many application it now is.
-
@Maciejasjmj said:
In a sensible language you'd also have a finally block to simplify matters, but in this case you can probably set checking to false in the catch clause just fine.
That's what the guards are for. Basically you write, once for all similar cases (or get somewhere), something like
...
Or you use something like
BOOST_SCOPE_EXIT
(which, with C++11 can be made significantly less ugly).
-
My general impression is that it's like he was complaining that modern car is more complex than model T while failing to mention that the modern car is faster and significantly safer.
For example, he's comparing C code of
struct foo { ... }; int foo_init () { ... } int foo_term () { ... } int foo_bar () { ... }
to C++ code like
class foo { public: foo () : state (semi_initialised) { ... } int init () { if (state != semi_initialised) handle_state_error (); ... state = intitialised; } int term () { if (state != initialised) handle_state_error (); ... state = semi_terminated; } ~foo () { if (state != semi_terminated) handle_state_error (); ... } int bar () { if (state != initialised) handle_state_error (); ... } };
But that is not equivalent. C++ equivalent of the C code would be just
class foo { public: int init () { ... } int term () { ... } int bar () { ... } };
It would die hard if the user failed to call
init
andterm
, but so does the C version. The extra code in the C++ version is a cost, but it provides some benefit too, namely additional safety.Besides, I can't think of a case where extra variable would be needed. Initialization can only fail if the object holds some resources and a NULL handle can be used to indicate the object was not (successfully) initialized.
Even more so, deinitialization generally shouldn't fail. What do you do if releasing a resource fails? It can only happen if you cardinally screwed up and are passing invalid handle, in which case trying to salvage it is usually futile, because your state is likely fubar anyway.
Exception is operations that flush and close a resource like
fclose
(3). In this case, the destructor should probablyassert
(3) in debug to make the mistake of forgetting to call the finalizer easier to find (which, again, C can't do at all) and just log and discard it in release (to make the code resilient). It's still better than leaking the handle.
-
Fortunately this problem only matters for system libraries and frameworks.
What do you think some of us are supporting?
-
[…] the very ideology of the object-orientedness makes developers think in specific ways and design their algorithms and data structures accordingly.
I think this might be his primary and main fault. C++ is not about object-orientation. It is about implicit resource management (RAII) and generic programming and it also happens to support object oriented stuff.
std::list <person*> people;
The most stupid think in this is the
*
. If he put the object in the list by-value, as is usual (unless you need dynamic polymorphism, which in C is pain anyway), the result would be equivalent to the C version and simpler and safer.
-
What do you think some of us are supporting?
Of course I understood you are supporting a library. And yes, I hate the fact maintaining stable library ABI is pain. And even more hate the way Microsoft runtime versions are mutually incompatible (gcc had some ABI transitions too, but it's been quite long since the last one). But it's still relatively small part of all work done in C++.
In fact the product I work on also comes with a library, but we solved the problem by providing a thin library with C API that controls the actual application over IPC. Fortunately the API does not need to be that large.
-
C gives you qsort; C++ gives you std::sort. One of those is significantly faster than the other. (Hint: it's not qsort.)
Why might that be?
Is something preventing
qsort
's code from being optimized?
Something to do with its API or ABI?
-
In fact the product I work on also comes with a library, but we solved the problem by providing a thin library with C API that controls the actual application over IPC. Fortunately the API does not need to be that large.
I'm supporting a programming language that's pretty widely used industrially. We have our own API→ABI compiler that allows us to keep it extremely stable, much more so than you'd usually get with C (let alone C++, which is far too keen to spray knowledge of things like type sizes around). Some of the techniques we used were inspired by what the Windows API does, and others are based on what ELF dynamic linkers do (but ported to be cross-platform and coupled into our version management infrastructure).
20 year support of any interface is not simple, especially when you're wanting to be able to evolve that interface as well…
-
Is something preventing qsort's code from being optimized?
There are other sorts that are potentially faster (qsort depends heavily on having good partitioning) and if they're comparing the sorting of
int
vsint*
there's a whole world of possibilities for complexity right there. (Cache coherency is the most likely candidate for trouble, and that's not something that most performance analyses consider.)
-
There are other sorts that are potentially faster (qsort depends heavily on having good partitioning)
I wouldn't think qsort necessarily needs to be implemented as a quicksort.So if I'm reading you correctly, the problem here is that C doesn't support templates and you can't specialize with different algos for different classes?
-
Is something preventing qsort's code from being optimized?
Yes. The use of function pointers.
std::sort
is a template. Those are code-generated for each instantiation and the substituted type defines what to call for comparison, so everything is there for the compiler to inline it. On the other handqsort
uses function pointers and is compiled just once and lives in the library. This means it is not available for the compiler for inlining.It's a trade-off. The
std::sort
is almost always inlined, so it's faster, but there is more code. Theqsort
exists just once, but it is slower due to the overhead of calling otherwise trivial function (comparisons usually are very simple) via indirection.
-
Because a linked list is almost always the wrong datastructure, especially for a modern target CPU.
That's nice. They're still pretty common, and I'm not sure why the "circular list with a linear-list interface" isn't a larger proportion. (Or maybe they are. Who knows.)The "next" and "prev" items probably aren't in the CPU cache, and might even be paged out.
This is totally true. Not just that, but the next/prev pointers themselves decrease cache density. This can make something like vectors faster even in cases where it seems like a list "should" be faster.(An example article by Stroustrup.)
As you can't even work out how many you have without walking the whole list, they are ludicrously slow.
Get a less awful list implementation.OK, that's a bit , but plenty of linked list implementations explicitly track the size, and it's not hard to do it yourself with a homegrown list.
-
It's not a “distinguished end”, it's either head (if the list has a head) or it's the first element again.
"Distinguished head" is probably a better term, but "end" isn't wrong. :-)
-
gcc had some ABI transitions too, but it's been quite long since the last one
Two nitpicks.First, note that there is one going on right now: C++11 mandates some behavior changes that require ABI changes. Now, new features allow that to be applied somewhat on-demand -- a preprocessor macro can control which you get -- but I think it is default with
-std=c++11
in the latest GCC version. (I could be wrong.)Second, while what you say is true for recent versions supporting the API of older versions, at least IME the reverse is not very true. It's extremely easy to write some source file that will compile with an older (4.x) GCC and run on a system with a recent one, but not vice versa.
-
On the other hand qsort uses function pointers and is compiled just once and lives in the library. This means it is not available for the compiler for inlining.
Wrong. GCC is able to inline some function pointer calls, eliminating indirection. And I believe Clang and MSVC can do it too.
-
Wrong. GCC is able to inline some function pointer calls, eliminating indirection. And I believe Clang and MSVC can do it too.
Your statement is not wrong, but your conclusion is. GCC and others can inline calls through function pointers if they can determine what the target is, or speculatively optimize (including inlining) based on profiling information.But inlining the call to the comparison function requires that there be something to inline it into, and
qsort
is not available to the compiler or linker to do this, even with link-time optimization.If you don't believe me, then try it. Here it is with Ubuntu 14.04 and GCC 4.9 (not this isn't the default version of 14.04):
$ cat qsort.c #include <stdio.h> int compare(int* a, int * b) { return *b - *a; } int main() { static int elements[100] = {0}; qsort(elements, 100, sizeof(int), compare); return elements[0]; } $ gcc -flto -O3 qsort.c $ objdump --disassemble a.out | grep '<main>' -A10 0000000000400440 <main>: 400440: 48 83 ec 08 sub $0x8,%rsp 400444: b9 70 05 40 00 mov $0x400570,%ecx 400449: ba 04 00 00 00 mov $0x4,%edx 40044e: be 64 00 00 00 mov $0x64,%esi 400453: bf 80 10 60 00 mov $0x601080,%edi 400458: 31 c0 xor %eax,%eax 40045a: e8 b1 ff ff ff callq 400410 <qsort@plt> 40045f: 8b 05 1b 0c 20 00 mov 0x200c1b(%rip),%eax 400465: 48 83 c4 08 add $0x8,%rsp 400469: c3 retq
Note the call to
qsort@plt
at 0x40045A. The@plt
means it's doing a dynamic call, in this instance in toglibc
. (Guess what's not inlined in glibc's version?)If you're wondering if
-static
will change this, it doesn't:$ gcc -g -static -flto -O3 qsort.c $ gdb a.out --quiet Reading symbols from a.out...done. (gdb) b compare Breakpoint 1 at 0x4010a0: file qsort.c, line 5. (gdb) r Starting program: /<censored>/a.out Breakpoint 1, compare (a=0x6c0cc4 <elements+4>, b=0x6c0cc8 <elements+8>) at qsort.c:5 5 return *b - *a; (gdb) p $rip $1 = (void (*)()) 0x4010a0 <compare> (gdb) up #1 0x0000000000406fb1 in msort_with_tmp.part () (gdb) quit A debugging session is active. Inferior 1 [process 31349] will be killed. Quit anyway? (y or n) y $ objdump --disassemble a.out | grep '4010a0:' -B2 -A10 00000000004010a0 <compare>: 4010a0: 8b 06 mov (%rsi),%eax 4010a2: 2b 07 sub (%rdi),%eax 4010a4: c3 retq 4010a5: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 4010ac: 00 00 00 4010af: 90 nop 00000000004010b0 <__libc_start_main>: 4010b0: 41 56 push %r14 4010b2: b8 00 00 00 00 mov $0x0,%eax 4010b7: 4d 89 c6 mov %r8,%r14 $ objdump --disassemble a.out | grep '406fb1' -B2 -A2 406fab: 4c 89 e7 mov %r12,%rdi 406fae: 41 ff d5 callq *%r13 406fb1: 85 c0 test %eax,%eax 406fb3: 7f cb jg 406f80 <msort_with_tmp.part.0+0x260> 406fb5: 41 8b 04 24 mov (%r12),%eax
See the
callq *%r13
at 0x406FAE? That's the indirect call tocompare
insideqsort
.
-
I forgot that I had more to say.
My previous post illustrates that GCC doesn't do the optimization of which we speak. What about could it? Technically speaking, it could. If the source (IR, really) of
qsort
were available, you could imagine the compiler performing this optimization.However, there is a really big difference between what needs to be done to optimize
std::sort
vs.qsort
. Without inlining, there is just one copy ofqsort
, but there are potentially multiple instantiations ofstd::sort
. In the latter case, assuming idiomatic C++, the instantiation being called completely determines the sort -- the concrete comparator parameter that is passed to it is completely ignored.In the
std::sort
case, all you need to do to speed things up is inline the comparator'soperator()
function. Since inlining is something compilers do already (and contributes a large proportion of the benefit of optimization), this presents no problem.But
qsort
is different. If you have multipleqsort
calls, the compiler can't inline the comparator call if it keeps just the one version ofqsort
. So the compiler needs to duplicate it.There are two ways this could come about. The first is if the duplication happened as a result of
qsort
itself being inlined into its callers. Butqsort
is a very complicated function, and I can't imagine it would be a candidate for inlining. So that option is out.The second option is the compiler could see it is called with different parameters in different contexts, and create multiple copies, and then optimize each for its calling context. The term for (the second part of) this is "partial evaluation". But I don't know of any compiler that implements a partial evaluator as an optimization, except so far as you would consider some optimizations like constant propagation being a special case of constant propagation.
The last hope arises only if your program has a single call to
qsort
. Maybe the compiler could detect this case and optimize it under that knowledge. This is actually a pretty interesting idea -- might be a neat project for someone to investigate as part of a research project or something like that -- but again,I've never heard of compilers doing something like this.Actually, it looks like GCC does do this, at least in certain circumstances! (Probably, it has to be bestatic
or maybe with LTO.)So in short... not only does GCC seem to not make that optimization currently, but it is extremely unlikely to change any time soon.
-
If it is in a shared library, and there is no source for it, the compiler can't inline it. It can inline things from static libraries (with link-time optimization), but not from shared ones. Not the least because it would change the semantics of the shared library, which it must not do.
And note, that distributions generally don't support static linking of libc at all for at least three different reasons.
-
Oh, right, forgot that library functions cannot be optimized almost at all - even moving sqrt() on effectively constant value outside the loop requires some fancy shmancy compiler flags to work.
-
If it is in a shared library, and there is no source for it, the compiler can't inline it.
library functions cannot be optimized almost at all
I was going to say this:For the reason that I realized when I was halfway through the first post and then wrote up in the second, I think the
glibc
thing is actually a red herring -- the compiler wouldn't optimize it even if it had the source.And that is mostly right, but I tried it and I was wrong about one aspect: It is true if you make two calls with different comparator functions -- it will not produce specialized & partially-evaluated versions of the functions for each. But if you have just one call, it actually will.
-
the compiler wouldn't optimize it even if it had the source.
No, it probably wouldn't. But you probably could force it to do so with suitable
__attribute__
s. When it's in a shared library, it is not technically possible.And that is mostly right, but I tried it and I was wrong about one aspect: It is true if you make two calls with different comparator functions -- it will not produce specialized & partially-evaluated versions of the functions for each. But if you have just one call, it actually will.
If you make two calls with different comparator functions, it won't make specialized versions, because they are the same instance and the body is beyond inlining limit. However when you make two calls with different comparator functors, it will, because there is only once call of each instance. Which is why C++ hackers often prefer functors over function pointers.
-
If it is in a shared library, and there is no source for it, the compiler can't inline it.
But the dynamic loader could, as it has all the information available to it about what the real code is. It still won't for
qsort
, but that's because it's a recursive algorithm, and there's absolutely no benefit in trying to get that smart. You really don't save very much.More trickily, there are reasons for not doing it that are independent. The C++ approach makes the eventual object code much larger (because of all the template specialization) and that can cause icache problems, which is something of a problem with many larger programs. The overall cache performance isn't something that you can really analyse by looking at just the algorithm; it's very closely tied to the details of how it is implemented in actual code. This sort of thing is why there's a difference between benchmark code and production applications, where the cost of having more levels of indirection (especially in things that might be actually nicely in your L1 cache) are offset by having less need to fetch code from main memory in the first place.
Caches: making algorithmic analysis harder since 1984…
-
But the dynamic loader could, as it has all the information available to it about what the real code is.
C doesn't have JIT, as far as I know.
-
That has nothing to do with C. The operating system or the CPU would have to have it for the native
bytemachinecode.
-
even moving sqrt() on effectively constant value outside the loop requires some fancy shmancy compiler flags to work.
The only thing required is metadata that says that sqrt() is a function whose result depends only on its arguments. Otherwise there's nothing really to distinguish it from rand() from the perspective of the compiler, which is very much not a safe function to optimise out of a loop that way.
-
C doesn't have JIT, as far as I know.
There are people working on it, as an extension of LTO. I believe it's still highly experimental.
-
The C++ approach makes the eventual object code much larger
The C++ approach has more problems. It also means that
ifwhen a bug is found instd::sort
, all programs have to be recompiled to get the fix while programs usingqsort
will immediately get the fixed version when libc is recompiled.
-
There are people working on it, as an extension of LTO. I believe it's still highly experimental.
Or we'll just switch to having most binaries in LLVM bytecode. After all, it's the same backend (as clang), just deferring the final assembly to just-in-time-time.
-
That has nothing to do with C. The operating system or the CPU would have to have it for the native bytemachinecode.
And neither they have JIT. AFAIK, of course.
-
The only thing required is metadata that says that sqrt() is a function whose result depends only on its arguments.
Except it doesn't, because something something FPU errors. Can't find it now, but I've seen a nice explanation on SO once, complete with appropriate GCC flag -f-treat-math-h-functions-like-the-result-for-same-params-is-always-the-same (named a little different) that made it behave like I would expect.
-
Oh, and, all the required metadata for sqrt() is in place - GCC devs are rather competent people.
-
Or we'll just switch to having most binaries in LLVM bytecode. After all, it's the same backend (as clang), just deferring the final assembly to just-in-time-time.
That would be workable; the runtime code issuer is pretty fast, though the main optimiser itself is a bit slower (it's currently tuned for non-JIT use).
-
Except it doesn't, because something something FPU errors.
Floating point exceptions. For people who are scared of NaN…
-
When it's in a shared library, it is not technically possible.
I'm not sure about this, but just becauseqsort
is provided byglibc
doesn't mean it only has to be provided byglibc
, no? So the compiler could get source toqsort
when compiling both the client program and the library, and inline calls when it's called in the program?Or would ELF symbol preemption/visibility/etc. rules require that the compiler implement it such that someone could preempt
qsort
with one from a different DSO, which would disqualify that optimization?However when you make two calls with different comparator functors, it will, because there is only once call of each instance.
Huh, I didn't knowqsort
took a functor parameter.The C++ approach makes the eventual object code much larger (because of all the template specialization) and that can cause icache problems
That's a general problem with templates, but unless you're rapidly switching between a bunch of smallsort
calls with different types I don't think it applies much in this case.C doesn't have JIT, as far as I know.
I think a C/C++ JIT might be an amazingly awesome project. If I were to start a PhD project knowing what I know now and with a free choice of project, that might be an option. (I know of a bunch of systems that do JIT-like things, but for reasons other than performance.)
-
I don't think it applies much in this case.
Except that it applies to lots of other things too throughout C++. It's not one thing, it's lots of them, and it all adds up. Systems which limit template polymorphism to reference types can much more easily share code (which obviously has consequences).
As every small child needs to learn for themselves about playgrounds: what time you gain on the swings, you lose on the roundabouts.
-
[…] doesn't mean it only has to be provided by glibc, no?
No; if the header had full code for inlining, it could be inlined. But the header does not have it.
Huh, I didn't know qsort took a functor parameter.
qsort
does not.std::sort
does, though. They are otherwise quite similar code.
-
I think a C/C++ JIT might be an amazingly awesome project.
There's already people working on it as part of LLVM (and yes, I think it's part of their PhD in compiler system design). I think they're also looking at decompilation so that they can convert object code back to IR, as that would greatly increase the amount that the LTO and JIT could do.
I'm leeching off some of that in my own project tinkering with LLVM. :D
-
I'm not sure about this, but just because
qsort
is provided byglibc
doesn't mean it only has to be provided byglibc
, no? So the compiler could get source toqsort
when compiling both the client program and the library, and inline calls when it's called in the program?Or would ELF symbol preemption/visibility/etc. rules require that the compiler implement it such that someone could preempt
qsort
with one from a different DSO, which would disqualify that optimization?GCC (and I'm pretty sure others) already perform similar optimizations for well-known functions. E.g., for
memcpy()
:void copy( int* aDest, int const* aSrc ) { memcpy( aDest, aSrc, 32 ); }
becomes
copy: movq (%rsi), %rax movq %rax, (%rdi) movq 8(%rsi), %rax movq %rax, 8(%rdi) movq 16(%rsi), %rax movq %rax, 16(%rdi) movq 24(%rsi), %rax movq %rax, 24(%rdi) ret
So, no call to
memcpy()
there. There's a number of standard functions for which this occurs (but maybeqsort()
isn't one of them).
-
but maybe qsort() isn't one of them
It definitely won't be. The core of it is a recursive function because it's doing recursive decomposition of the array to be sorted. It would be doing that even if it wasn't actually using the quicksort algorithm; recursive decomposition is required to build a fast general sort. You don't inline a recursive function; it literally makes no sense to do so at all (unless you can compile-time bound the recursion depth, but that would be silly for sorting).
There might be a wrapper that would be
inline
able, but there's very little gain from that. Function calls aren't really all that expensive. (It's not like they're memory allocation or I/O…)
-
I'm not sure about this, but just because qsort is provided by glibc doesn't mean it only has to be provided by glibc, no?
Inlining dynamic libraries kinda violates API contract. And LGPL license.
-
@Jaloopa's first law: Any forum thread where C++ is mentioned will quickly turn into an in depth discussion about undefined behaviour, exactly what a particular version of a particular compiler does internally with some specific code, and other implementation details that are unreadable to anybody who uses a sensible programming language.
Pithy, innit
-
Specifically, this may happen for functions of which GCC has a builtin version. It's probably in the documentation.
-
It definitely won't be. The core of it is a recursive function because it's doing recursive decomposition of the array to be sorted. <snip />
No, but a compiler could -in theory- emit a custom instance of
qsort()
where the comparator is inlined, and call that instead of the ordinaryqsort()
(assuming that the comparator is known at the location whereqsort()
is invoked).
-
@Jaloopa's first law: Any forum thread where C++ is mentioned will quickly turn into an in depth discussion about undefined behaviour, exactly what a particular version of a particular compiler does internally with some specific code, and other implementation details that are unreadable to anybody who uses a sensible programming language.
C++ threads automatically turning into in-depth technical discussions? To me that sounds like quite a nice feature. ;-)
-
It's been way too long since someone posted dumb animal noises.
-
@Jaloopa said:
@Jaloopa's first law: Any forum thread where C++ is mentioned will quickly turn into an in depth discussion about undefined behaviour, exactly what a particular version of a particular compiler does internally with some specific code, and other implementation details that are unreadable to anybody who uses a sensible programming language.
C++ threads automatically turning into in-depth technical discussions? To me that sounds like quite a nice feature. ;-)
I
takethrow exception to this.Which means all the posts in the thread have to be deleted.
-
I
takethrow exception to this.Which means all the posts in the thread have to be deleted...
... and the unhandled exception terminates the server process with extreme prejudice? Isn't that like a core discourse feature?
-
That would explain all the 503 errors.
-
@Jaloopa's first law: Any forum thread where C++ is mentioned will quickly turn into an in depth discussion about undefined behaviour, exactly what a particular version of a particular compiler does internally with some specific code, and other implementation details that are unreadable to anybody who uses a sensible programming language.
Pithy, innit
Everything wrong with @Jaloopa's First Law
in 2 minutes or less
(spoilers)
(duh)
_- This law doesn't apply in the thread when it was first announced, because the original post is specifically about C++ caveats. *ding*
- He is the first one to ever mention UB in this thread. *ding*
- He's Java programmer. How do I know? Because the talk about compiler optimizations is incomprehensible to him, and Java programmers are most notorious of not caring about performance ABSOLUTELY AT ALL. *ding*
- He's Java programmer. That deserves two sins. *ding* *ding* Three, even.
- I don't see anyone here mentioning particular compiler versions. *ding*
- He tries to be funny by imitating @blakeyrat but fails miserably. *ding*
- He caused me to summon @blakeyrat here, who will now blakerant about how incomprehensible this all shit is to everyone except "C++ 1337h4xx0r dickheads", as he would probably say it. *ding*
(bonus sin if @blakeyrat rants at me for writing up those two points above since he's already got dragged in this topic, instead of what I said here) - The law implies that talking about compilers on programming forum is spamming *ding* or otherwise inappropriate *ding*.
- I was probably wrong about the Java thing - in that case, he's terrible programmer in whatever language he uses because he doesn't care about performance. AT. ALL. *ding*
- He's still Java programmer in heart. *ding*
- The word "innit" doesn't exist. *ding*
- None of the definitions on Urban Dictionary for "innit" mentions anything sexual. Congratulations, you made me lose a bet with a friend. I'll count each definition as a sin. *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding* *ding*
- The wording of the law makes you think C++ is the only language that instantly summons overly technical discussions. Hint: it's not. *ding*
- His only post in this whole topic is a hate post. *ding*
- No period after the last sentence in his post. *ding*
TOTAL SIN TALLY: 44
SENTENCE:
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
C++ CODE REVIEWS
(in a project using boost::spirit)
-
1. This law doesn't apply in the thread when it was first announced, because the original post is specifically about C++ caveats. ding
It was mentioned, wasn't it?
2. He is the first one to ever mention UB in this thread. ding
It's an example of the kind of thing that happens in C++ threads3. He's Java programmer. How do I know? Because the talk about compiler optimizations is incomprehensible to him, and Java programmers are most notorious of not caring about performance ABSOLUTELY AT ALL. ding
Nope4. He's Java programmer. That deserves two sins. ding ding Three, even.
Nope6. I don't see anyone here mentioning particular compiler versions. ding
Again, an example of what happens in these threads7. He tries to be funny by imitating blakeyrat but fails miserably. ding
If you didn't find it funny, I guess I'll give you that one8. He caused me to summon blakeyrat here, who will now blakerant about how incomprehensible this all shit is to everyone except "C++ 1337h4xx0r dickheads", as he would probably say it. ding
Don't blame me dude, you summoned him9. The law implies that talking about compilers on programming forum is spamming ding or otherwise inappropriate ding.
Not the intention, just an observation11. I was probably wrong about the Java thing - in that case, he's terrible programmer in whatever language he uses because he doesn't care about performance. AT. ALL. ding
I care about performance. I don't get worked up about exactly what happens under the hood unless I need to to know for the performance improvement I'm chasing12. He's still Java programmer in heart. ding
C++ was the first language I used. Damn, you really hate Java don't you?13. The word "innit" doesn't exist. ding
UK slang. Short for "isn't it"14. None of the definitions on Urban Dictionary for "innit" mentions anything sexual. Congratulations, you made me lose a bet with a friend. I'll count each definition as a sin. ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding ding
Don't make stupid bets then42. The wording of the law makes you think C++ is the only language that instantly summons overly technical discussions. Hint: it's not. ding
Maybe. I tend to notice it mostly in C++, but that's probably confirmation bias43. His only post in this whole topic is a hate post. ding
I'm a lover, not a hater44. No period after the last sentence in his post. ding
To be a proper EWW, this should have called me racistThanks for playing