if (posix_fallocate(fd, 0, size)) {
errno is not set.

Well, that is horrible. Something might have fixed POSIX like... making entry points too long to invoke in a conditional and then every example of the API involve "NTSTATUS status = ..." then "if(NT_SUCCESS( status ))"... Though I'm not trying to sing anyone's praises here, I actually find that sort of "forced error handling" much clearer than relying on implicit behavior. Of course it can be made just as bad and unstable. However, neither ERRNO or NTSTATUS are in any way the same as a 3 (or 4) element set.

It is not that POSIX APIs are inconsistent from one to another, it is that they are documented and users don't read the documentation. I'm no saint here either, though this is also why we test. No degree of semantic enforcement is going to spare one from making mistakes like those. Worse still, their implementations may be inconsistent or unstable to the extent of forcing incorrect usage.

Combination is particularly important for generic code: how do you write
operator<=> for std::vector<T> or std::tuple<T...> ? For tuples, it's a
heterogeneous list, so the types may be weakly ordered, strongly ordered or
only partially ordered. So what is the result of the three-way compare?

Straightforwardly apply <=> to each pair of members in succession, like a string. Apply common_type to both before comparing in a tuple (this can apply to any structure). Take the first result that isn't equal. If they were all equal, then the result of the composition is equal. This exact same idea applies to multiple precision integers (or even in string comparison), where despite the data being heterogeneous, the comparison still has a consistent interpretation as "sign of difference". For example, you can envision the individual sequences of bytes in PODs contained in tuples as just plain integers.

If a particular sort is needed, like case-insensitivity, this requires a transformation to be applied. Wherever that transformation is applied, even if it is just setting a flag, is also where the change in the nature of the comparison can be noted. It is possible to change the sorting category of strings at runtime by just changing locales. A statically-compiled, semantically rich 3-way comparison enum isn't going to stop someone from setting a flag. As another example from numerics: Change a rounding mode at runtime. Now going from something like an MP::Rational to a double has a slightly different ordering over the doubles. How is this change in behavior enforced? By a predicate separate from both MP::Rational and double, which is also going to implement some kind of contextual awareness of this fact.

It is possible to keep std::strong_ordering around. Just formally allow <signed-int> operator<=>, and let the compiler do the rest. The alternative is really just saving around 12 lines of code per thing that needs a full suite of comparison operators.

On Tue, Sep 26, 2023 at 11:27 PM Thiago Macieira via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Tuesday, 26 September 2023 20:34:16 PDT Chris Gary via Std-Proposals wrote:
> I fail to see why a strongly-typed ternary, mandatory for use with <=>,
> provides an adequate solution to this problem. Especially where the
> operator is user-defined.
> All it does is create what I consider to be a nuisance of a dependency.

Because that's not the problem it solves.

No one is arguing that four values can't be represented in int. No one is
arguing that optimal code can't be generated.

The use of a strong type solves two problems that int doesn't: semantic and
combination. Semantic allows code to know the difference between equality in
strongly-comparable types and equivalence in weakly-ordered ones. You may not
care about that difference, but other people do. The strong typing also allows
one to know whether the result can be unordered or not: if it can't be
unordered, you don't need to implement the lookup tables you described at all.

Combination is particularly important for generic code: how do you write
operator<=> for std::vector<T> or std::tuple<T...> ? For tuples, it's a
heterogeneous list, so the types may be weakly ordered, strongly ordered or
only partially ordered. So what is the result of the three-way compare?

Moreover, semantic gives meaning where int doesn't. Let's take these two very,
very similar functions that return int from the C / POSIX library:

int memcmp(const void *, const void *, size_t);
int bcmp(const void *, const void *, size_t);

As we all know, memcmp() performs a three-way comparison and returns negative,
zero, and positive for less than, equal, and greater than byte comparisons.
bcmp() does not. It returns zero for equal and non-zero otherwise. But does
the return type convey that meaning? No. Will people mistake one for the
other? They most surely will.

And have. In fact, since in most C libraries bcmp() is implemented as an alias
to memcmp(), it has returned negative and positive as people might have
expected. This is so bad that when glibc attempted to implement a slightly
faster bcmp() that didn't return negative/positive, it broke all sorts of user
code. So glibc added __memcmpeq() instead.

Plain integers are bad to convey semantic meaning. I'm just going to give
another example that we found lurking in our codebase for 3 years:

    if (posix_fallocate(fd, 0, size)) {
        int saved_errno = errno;
        log_warning("could not create temporary file: %s", strerror(errno));
        close(fd);
        return -saved_errno;
    }

See if you can spot the error.

PS: it's slightly ironic that memcmp() would not be a good example of your
suggestion, in spite its returning an int.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
   Software Architect - Intel DCAI Cloud Engineering



--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals