Live data from Hacker News

Three new utility functions in C++23

mariusbancila.ro

181–190 of 196 posts

Re: Three new utility functions in C++23

#181
post #137

Earlier quoted context omitted.

What do you mean? Sorting is done by swapping elements ( swap(a[i], a[j]) )

Well, first, whether swap() is used or not depends on the algorithm; second, the result of the sorting, as opposed to reversal, does not necessary look like the elements were swapped.

Oh, based on your other comment, I finally understood what you meant initially.

You were saying that the final array doesn't have many indices i,j such that a[i] = sorted_a[j] and a[j] = sorted_a[i].

My initial comment was not referring to the final order of the array, but the operations made to reach that order (one or multiple swaps). Another example could have been saying that we could have named the "sort" method "compare" because it uses comparisons in its algorithm (which was a parallel to your initial comment that it's called "swap" because the reverse operation uses swaps to achieve this).

Re: Three new utility functions in C++23

#182

Earlier quoted context omitted.

For that abort() is just fine. Being UB, unreachable is more about optimizations .

The literal example in the Clang documentation is not about optimization: > For example, without the __builtin_unreachable in the example below, the compiler assumes that the inline asm can fall through and prints a “function declared ‘noreturn’ should not return” warning. void myabort(void) __attribute__((noreturn)); void myabort(void) { asm("int3"); __builtin_unreachable(); }

> the compiler assumes that the inline asm can fall through and prints a "function declared 'noreturn' should not return" warning.

It actually can (Hint: what happens if the operating system `iret`s from its int 3 handler?), although it's probably not a issue in practice. Regardless, you don't need __builtin_unreachable to write:

  void myabort(void) __attribute__((noreturn));
  void myabort(void) {
    asm("int3");
    myabort(); // might need `return myabort();` to force TCO,
    // but gcc doesn't like that and it should work anyway
    }
  # Assuming tail-call optimization etcetera, this produces:
  myabort:
    int3
    jmp myabort
which is a correct implementation.

However, if you're implementing built-in/standard functions like abort, you presumably know what compiler you're using and don't need a std interface in the first place. There's zero legitimate reason to use a undefined-behaviour-based `unreachable` in application code.

Claiming std::unreachable is useful for implementing abort is like proposing a std::manual_copy function because your compiler optimized a implementation of memcpy to a call to itself - at some point you do in fact have to resort to implementaion-specific details to define the abstractions that abstract away said details, and "in literally the same function as the (also-nonstandard, IIRC) inline assembly that hopefully doesn't return" seems at if not noticeably past that point.

Re: Three new utility functions in C++23

#183
post #83

>Byte swapping is important when transferring data between system that use different order for the sequence of bytes stores in memory. That seems like a glaring footgun to me, to the point where I think I must be missing something. What I want when dealing with endianess are "from_little_endian/to_little_endian", "from_big_endian/to_big_endian" function pairs that expand to either nop or a byte swap depending on the…

The std::endian enum makes it very easy to find the native endianness.

That's irrelevant, because there's nothing useful you can do with the native endianness (other than implement (load/store)_(big/little)_N for various combinations of options).

Re: Three new utility functions in C++23

#184

In D an unreachable branch can be indicated with: assert(0); which is used frequently in D. This is a bonus from assert() being a builtin to D rather than a macro.

The problem with the C++23 std::unreachable is that it invokes undefined behaviour. Calling abort (or panic, or whatever D's assert boils down to when the condition fails), would be a prefectly reasonable way to define unreachable. (That is, for example, basically how I define it in my own code:)

  #define unreachable die("unreachable code reached")

Re: Three new utility functions in C++23

#185
post #10
post #7

Earlier quoted context omitted.

That's not a good advice. Only if the sender and receiver are guaranteed to be running on little endian architecture you can make such a claim. A better advice is to always consider the endian-ness when designing protocols and have a strategy to handle it.

How is that "better advice"? Big endian architectures are pretty much dead (x86, ARM and RiscV are all little endian; some ARM chips are bi-endian, but not Apple's) and there's no discernible compelling advantage that would allow a comeback. You absolutely want to specify the byte order in new protocols as little endian.

> and there's no discernible compelling advantage

Actually, for wire encodings, there is, although I've (I-think-)literally never seen any proponent of big endian bring it up (versus the bullshit "it's human-readable" nonsense[0][1]): big endian encodings of unsigned numbers have lexicographic order that matches their numeric order.

The most obvious concrete example of why this is useful is a keys-sorted encoding of a hash table: if you encode keys in size-type-value format, you can check sortedness by lexicographic order of type-value strings (which means you can add new types without old software needing to know how to compare them), and you'll get integer keys in inspection-friendly numeric order rather than semi-random order. (Encoding negative numbers with a type id of T_UINT-1 lets you extend this to them as well.)

At a more abstract level, where (zero-padded) little-endian numbers have the same value at different granularities, this means that big-endian numbers have invariant lexicographic order at different granularities: two strings viewed as bits, bytes, or uint32s are consistently in the same order.

You can kind of use reverse-lexicographic order for some of this, but there are obvious problems with sending data in value-type order rather than type-value, so forward-lexicographic tends to be strongly enforced.

0: "You mean for arabic numerals, except not actual arabic numerals, because Arabic is written right-to-left, so the numbers are little-endian there, but ended up big endian because they stayed least-signifiant-digit-right rather than least-signifiant-digit-first when imported into Latin."

1: "Also, so (supposedly) is decimal and sign-magnitude, but we've (agonizingly slowly) learned that those aren't good ideas."

Re: Three new utility functions in C++23

#186

Earlier quoted context omitted.

> Pragmas are processed by the pre-processor, so they aren't appropriate for expressing control flow hints. I don't thing this is remotely true. C++ pragmas were designed with the express purpose of providing additional information to compilers.

Attributes are better suited for that. #pragma has always just been a grandfathered in hack.

> Attributes are better suited for that. #pragma has always just been a grandfathered in hack.

I'm not so sure attributes are better. They have political traction, but that does not mean better. All major compilers use pragmas effectively to implement custom compiler flags. See for instance how Visual C++ uses pragmas extensively to toggle specific compiler warnings, not to mention the infamous #pragma once

Re: Three new utility functions in C++23

#187

Earlier quoted context omitted.

There are also conditional expressions to consider. With the function approach, you can effectively mark any subexpression as unreachable, so e.g. this is possible: auto x = (y == 1) ? foo : (y == 2) ? bar : ... std::unreadchable();

You could can 'inject' it via writing a function never_fails, that tests the condition, and branches to unreachable if it fails, then returns the same condition. That should let the compiler understand that the condition is always true.

Sure, you can adapt a different solution to this case - but why, when a magic function can be used everywhere a pragma or similar could, and also covers this case naturally? A pragma or an attribute would make more sense if you needed to do that in the middle of class declarations, say. But here, we're only concerned about executable code.

Nor is it unprecedented to have a standard library function triggering UB when called - it's just that this one has a precondition that's always false, so it's always UB.

Re: Three new utility functions in C++23

#188

Earlier quoted context omitted.

The sole intent of NDEBUG in the Standard is to suppress assert(). There's a reasonable argument to be made here that it's poorly named (although I suspect that ANSI simply standardized existing practice here, as they did with much of the standard).

Surely you mean ISO and not ANSI? Were such ugly hacks already a part of C89?

> Were such ugly hacks already a part of C89?

Yes.

Re: Three new utility functions in C++23

#189

Earlier quoted context omitted.

The sole intent of NDEBUG in the Standard is to suppress assert(). There's a reasonable argument to be made here that it's poorly named (although I suspect that ANSI simply standardized existing practice here, as they did with much of the standard).

Surely you mean ISO and not ANSI? Were such ugly hacks already a part of C89?

In fact, it turns out that it was already in K&R C ten years before C89:

https://github.com/dspinellis/unix-history-repo/blob/Researc...

Re: Three new utility functions in C++23

#190

Earlier quoted context omitted.

The std::endian enum makes it very easy to find the native endianness.

That's irrelevant, because there's nothing useful you can do with the native endianness (other than implement (load/store)_(big/little)_N for various combinations of options).

you can use it to implement native_to_be or native_to_le, it seems pretty useful to me. In fact, what can't it do?
Post reply on HN