Date: Sun, 8 Nov 2026 22:22:03 -0500
FYI
---------- Forwarded message ---------
From: Mark Hoemmen via Sci <sci_at_[hidden]>
Date: Sun, Nov 8, 2026 at 10:16 PM
Subject: Re: [isocpp-sci] P4461: The discombobulation of extensible math
functions
To: <sci_at_[hidden]>
Cc: Mark Hoemmen <mark.hoemmen_at_[hidden]>, Stéphane Gros-Lemesre <
stephane.groslemesre_at_[hidden]>, Jan Schultke <janschultke_at_[hidden]>
Hi Jan and Stéphane!
Jan wrote:
> If the generic interface says "this returns the square root" without
specifying any other details, that's all you have to work with.
std::linalg had an issue (LWG 4315,
https://cplusplus.github.io/LWG/lwg-defects.html#4315 ) about square
root. We ended up resolving it by making the two functions that use
square root not generic for now.
Of the four "if-needed" exposition-only functions, _`abs-if-needed`_
is most likely to do nontrivial computation. It follows the general
[linalg.reqs] design of "assume, don't constrain." (For examples of
"assume," see [numerics.defns] and how e.g., `reduce` uses
GENERALIZED_SUM. For the "constrain" approach that std::linalg
consciously did not take, see P1813, "A concept design for the numeric
algorithms.")
We took the "assume, don't constrain" approach for std::linalg because
we were focused on associativity. We didn't want to constrain, e.g.,
dot product on associativity of addition, because it wouldn't actually
be true for many number types. Of all the arithmetic types, only
unsigned integers have associative addition. Just like with
std::reduce, the implementation gets to reorder terms arbitrarily and
the caller gets whatever results from that.
Associativity isn't the only property we might want from an arbitrary
number type, though. Returning to _`abs-if-needed`_, we might like to
know that `x <= abs(x)` (if `x` is an instance of a type with some
ordering), or that `abs(x) == x or -abs(x) == x` (if the type has
unary negation). std::linalg didn't need those properties, so we
didn't try to assert them. The library only needs to compute
something with the result of _`abs-if-needed`_ in a few functions,
e.g., `vector_abs_sum`. Those functions just have a minimal Mandate
that a simple generic implementation would be well-formed.
std::linalg didn't need more specification of _`abs-if-needed`_ in
order for a straightforward generic implementation to make sense.
That's because it's just "BLAS in C++." It rarely does anything other
than add and multiply. Contrast this with a hypothetical "generic
LAPACK," which would need a whole set of number properties and a query
system analogous to `_LAMCH`. We weren't proposing a generic LAPACK,
so we didn't propose those facilities or requirements.
If instead I were proposing a generic `std::math::abs` or
`std::math::sqrt`, though, I would need to cover all possible use
cases, for all possible libraries that users might like to write. A
library like std::linalg uses a generic number interface that's
specific to its needs. Designing that std::linalg-specific interface
thus differs from designing a generic number interface for all
possible needs.
mfh
On Sat, Nov 7, 2026 at 2:10 PM Jan Schultke via Sci
<sci_at_[hidden]> wrote:
>
>
>
> On Sat, 7 Nov 2026 at 20:48, Stéphane Gros-Lemesre <
stephane.groslemesre_at_[hidden]> wrote:
>>
>> Hi Jan,
>>
>> Thank you very much for this paper.
>>
>> First, I want to state here that I'm a complete noob. Not a complete C++
noob, hopefully, but a standardisation noob.
>> I'll try to make my case as best I can, forgoing the expression of a
good amount of doubt and precaution in what I say. It is just to attempt to
make my point of view clear.
>> I fully acknowledge that there are for sure many aspects and
considerations I am not even aware of, and that must be handled with care
due to the one-way nature of standardisation.
>> In addition, I don't feel particularly strongly about this. I would
really like a way to customise math functions for custom types, but beyond
this, it's implementation details to me.
>
>
> Hey Stephane, nice to hear from you so quickly, and thanks for taking
the feedback so well.
>
>> I'm not sure that specifying how functions behave in their generic form,
that is, when they apply to types that are not currently specified, is
reasonable.
>> I might be wrong, but it seems to me that P4461's view mostly applies to
floating-point-like types. The sign of 0, the handling of NaNs or infinity,
etc.
>> When writing P4188, I had much simpler, but also broader use-cases in
mind.
>
>
> Well, the case study and most of the argument focuses on floating-point
just because we have concrete examples of that in the standard, but I think
you could extend those issues to other types. For example, since
fixed-point types typically don't have infinity or NaN, you would need to
think about how overflow works for those. Perhaps it could be saturating,
perhaps it could be throwing, perhaps it could just be undefined behavior.
So ... how does overflow work for fixed-point customizations of
std::math::exp2 then?
>
>> My point of view is that math functions are not, and should not be,
super special.
>> `std::abs(float)` is super special because float is quite special, and
IEEE 754 has super special rules, but `std::abs(int)` could be implemented
as `(i<0?-i:i)`, nothing special.
>> So, to me, std::abs is not special in itself. Only specific overloads
are special.
>> And I think that this is important and helpful for teachability: this is
not black magic, just an abstraction. It may or may not be implemented
using intrinsics, but ultimately, it's made of the same stuff as the rest
of the code.
>> In this sense, to me, math function overloads should not be different
from operator overloads.
>
>
> Sure, in its most basic form, abs can just be (i < 0 ? -i : i), although
you should use std::fabs, in practice. That example suits your argument
because there is only one "special" form of abs.
>
> However, did you know that we have std::max, std::fmax, std::fmaximum,
and std::fmaximum_num, all with subtly different handling of NaNs and
negative zero? You really wouldn't want to use a naive abs function for
floating-point and wouldn't want a naive max function either. But if an
abstract "max" customization point is implemented cleverly, which of these
maximum functions should be backing it?
>
> You may not have a std::math::max customization point in your paper
(maybe you should though?), but the point is that you can quickly run into
situations where a high-level mathematical operation turns into four
distinct concrete functions. I also don't think this is specific to
floating-point, and you're supporting the infinite set of user-defined
types after all.
>
>>
>> The specifics of the contract of a function hold for a specific
signature of that function, and not across all definition of that function.
>> `std::lower_bound` for instance changes its performance contract based
on the type it receives.
>
>
> That is true, but the change in performance is predictable. You're going
to get linear searches if you pass forward iterators and a better algorithm
if you pass random access iterators. Crucially, you can still reason about
the interface to a degree, and you don't need to look at implementation
details. With your std::math::sqrt, you really have no idea what it does
unless you look at the input type and the source code of the customization.
>
>>
>> And of course, I'll end this argument by mentioning operators.
>> P4188 intends to allow the specialisation of math functions for custom
types exactly in the context where these types would also overload
operators.
>> And operators come with no guarantee of any kind (apart from precedence,
associativity, and operand counts).
>> If I define my own float type, nothing is stopping me from implementing
operator* by Newton's method.
>> So... should we also specify every operator? And if so, what do we do
about `std::string` `operator+`?
>>
>> To me it should be enough to specify the behaviour and contract for
native type and std-lib types.
>
>
> Sure, people can do any silly thing they want with operators. The
operator is merely a symbol with some vague connotation, and then people
can imbue that symbol with any meaning they want.
>
> If providing a symbol is all there is to the paper, I see little
motivation for standardization though. I think it induces discombobulation
if library authors are presented with this symbol and have almost absolute
freedom in giving it meaning.
>
>>
>> My second point is about the pragmatic side of "How would we enforce
these aspects?"
>> We could maybe write some recommendations, saying it'd be nice if for
types for which it applies, signaling NaNs were handled in such and such
way.
>> Or we could go as far as saying that if they don't, it's a
non-std-conformant implementation.
>> To me, this amounts at shaking a finger. I believe it would have very
little impact in practice, unless it is somehow enforced.
>
>
> There is a fair amount of precedent for these things in the standard. We
have named requirements such as ForwardIterator or MoveAssignable which
require library authors to not only make the expression well-formed but to
also make them do the right thing. If they don't, it's undefined behavior.
One could argue that it's not really enforced if it's unchecked UB, but at
least there is a clear answer as to what things do.
>
>>
>> Suppose I am writing a custom type. I overload my operators, then I
specialise the math functions I expect will be needed...
>> If I suddenly realise that, although it works as I want it to in the
use-cases I have planned for this custom-type, I can't make my
implementation std-conformant trivially... what are the chances I'll merely
write a comment about it, or add an assert in the function body? That would
very much depend on the scope I have planned for that custom type, I guess.
>>
>> I think I understand the argument that if we lose all specification,
then the usage we can make of the type becomes more limited.
>> I would argue that, assuming we could test the compliance to these
aspects, maybe the test should be on the side of the generic code using
generic numeric types, rather than on the types themselves.
>> The reason for that being that different algorithms require different
aspect, and can be indifferent to other aspects.
>
>
> Users ultimately program against interfaces. If the generic interface
says "this returns the square root" without specifying any other details,
that's all you have to work with.
>
> This is going in a fairly unusual inverse direction where the user
chisels away at the details of the operation.
>
>>
>> Regarding the introduction of `fast_sqrt` and `cr_sqrt`, I believe they
should be the object of a different proposal, as they could live alongside
`std::sqrt`.
>> I can't help mentioning that I personally dislike this proposal, as I am
concerned with the scalability of this approach. The provided example
already suggests the possible addition of a `cr_fast_sqrt` which would
sacrifice precision to speed while remaining correctly rounded.
>
>
> cr_fast_sqrt is an oxymoron; "correctly rounded" means that the result is
always the most precise, down to the last bit. You cannot sacrifice any
precision to speed and remain correctly rounded.
>
>>
>> But I'd rather save this debate for a later time. I think I understand
the argument that if we added functions with a narrower contract later, how
would they interact with other custom types.
>> I believe that C++ has many contracts that are expressed as "equivalent
or narrower" instead of strictly narrower to avoid such problems.
>>
>>
>> Anyway, my suggestion would be to have traits.
>> If we don't want to (and can't, really) constraint the implementation,
but wish to offer a way to know the specific aspects of the implementation,
we could offer traits that can be opted-in to state the contract.
>> Custom types that merely wrap a native type, or are limited to internal
well-scoped usage where it doesn't matter could be implemented quickly,
while types meant for publication in published libraries would carefully
document their contract.
>> Generic code would then be able to chose whether they require specific
aspects.
>> Would that make sense?
>
>
> So if I understand it correctly, for example, you would have a trait that
lets you figure out whether a std::math::sqrt customization is correctly
rounded rather than having a correctly rounded std::math::cr_srt. There are
a few problems with this:
>
> You only get a single function, so the user doesn't get to choose between
a correctly rounded and an imprecise version. They can only tell which one
they're working with, but what happens when they don't like what they see?
Generic code could at best static_assert that the operation has the right
characteristics, but there is only one sqrt.
> This is kind of like a std::numeric_limits on a per-operation basis. It
seems fairly high-effort to maintain something like this, and it may be
tricky for us to extend it later, depending on the design.
>
> Anyway, all of this seems technically possible, but it seems both hard to
use and hard to customize when you are interested in additional details. As
a user, you need a bunch of assertions to make sure the operation fits your
use case, and as a library author, you need an extra trait providing
possibly a long list of details about your operation.
>
> It also doesn't solve the key problem pointed out in my paper that
library authors don't know what to do and don't know what to maximize for
(speed/correctness/C-compatibility). These traits would merely document
choices but don't help with making right choices.
>
>> Again, please don't take that as "the Truth according to SGL". I am very
aware of the limits of my understanding of the matter I'm approaching, and
for that reason I am very grateful for the attention and effort you have
put into your response paper and our conversations.
>
>
> If everyone in the committee was as humble and nice as you, C++26 would
have shipped already.
>
>
> Yours
> Jan
>
> _______________________________________________
> Sci mailing list
> Sci_at_[hidden]
> Subscription: https://lists.isocpp.org/mailman/listinfo.cgi/sci
> Link to this post: http://lists.isocpp.org/sci/2026/11/0930.php
_______________________________________________
Sci mailing list
Sci_at_[hidden]
Subscription: https://lists.isocpp.org/mailman/listinfo.cgi/sci
Link to this post: http://lists.isocpp.org/sci/2026/11/0931.php
---------- Forwarded message ---------
From: Mark Hoemmen via Sci <sci_at_[hidden]>
Date: Sun, Nov 8, 2026 at 10:16 PM
Subject: Re: [isocpp-sci] P4461: The discombobulation of extensible math
functions
To: <sci_at_[hidden]>
Cc: Mark Hoemmen <mark.hoemmen_at_[hidden]>, Stéphane Gros-Lemesre <
stephane.groslemesre_at_[hidden]>, Jan Schultke <janschultke_at_[hidden]>
Hi Jan and Stéphane!
Jan wrote:
> If the generic interface says "this returns the square root" without
specifying any other details, that's all you have to work with.
std::linalg had an issue (LWG 4315,
https://cplusplus.github.io/LWG/lwg-defects.html#4315 ) about square
root. We ended up resolving it by making the two functions that use
square root not generic for now.
Of the four "if-needed" exposition-only functions, _`abs-if-needed`_
is most likely to do nontrivial computation. It follows the general
[linalg.reqs] design of "assume, don't constrain." (For examples of
"assume," see [numerics.defns] and how e.g., `reduce` uses
GENERALIZED_SUM. For the "constrain" approach that std::linalg
consciously did not take, see P1813, "A concept design for the numeric
algorithms.")
We took the "assume, don't constrain" approach for std::linalg because
we were focused on associativity. We didn't want to constrain, e.g.,
dot product on associativity of addition, because it wouldn't actually
be true for many number types. Of all the arithmetic types, only
unsigned integers have associative addition. Just like with
std::reduce, the implementation gets to reorder terms arbitrarily and
the caller gets whatever results from that.
Associativity isn't the only property we might want from an arbitrary
number type, though. Returning to _`abs-if-needed`_, we might like to
know that `x <= abs(x)` (if `x` is an instance of a type with some
ordering), or that `abs(x) == x or -abs(x) == x` (if the type has
unary negation). std::linalg didn't need those properties, so we
didn't try to assert them. The library only needs to compute
something with the result of _`abs-if-needed`_ in a few functions,
e.g., `vector_abs_sum`. Those functions just have a minimal Mandate
that a simple generic implementation would be well-formed.
std::linalg didn't need more specification of _`abs-if-needed`_ in
order for a straightforward generic implementation to make sense.
That's because it's just "BLAS in C++." It rarely does anything other
than add and multiply. Contrast this with a hypothetical "generic
LAPACK," which would need a whole set of number properties and a query
system analogous to `_LAMCH`. We weren't proposing a generic LAPACK,
so we didn't propose those facilities or requirements.
If instead I were proposing a generic `std::math::abs` or
`std::math::sqrt`, though, I would need to cover all possible use
cases, for all possible libraries that users might like to write. A
library like std::linalg uses a generic number interface that's
specific to its needs. Designing that std::linalg-specific interface
thus differs from designing a generic number interface for all
possible needs.
mfh
On Sat, Nov 7, 2026 at 2:10 PM Jan Schultke via Sci
<sci_at_[hidden]> wrote:
>
>
>
> On Sat, 7 Nov 2026 at 20:48, Stéphane Gros-Lemesre <
stephane.groslemesre_at_[hidden]> wrote:
>>
>> Hi Jan,
>>
>> Thank you very much for this paper.
>>
>> First, I want to state here that I'm a complete noob. Not a complete C++
noob, hopefully, but a standardisation noob.
>> I'll try to make my case as best I can, forgoing the expression of a
good amount of doubt and precaution in what I say. It is just to attempt to
make my point of view clear.
>> I fully acknowledge that there are for sure many aspects and
considerations I am not even aware of, and that must be handled with care
due to the one-way nature of standardisation.
>> In addition, I don't feel particularly strongly about this. I would
really like a way to customise math functions for custom types, but beyond
this, it's implementation details to me.
>
>
> Hey Stephane, nice to hear from you so quickly, and thanks for taking
the feedback so well.
>
>> I'm not sure that specifying how functions behave in their generic form,
that is, when they apply to types that are not currently specified, is
reasonable.
>> I might be wrong, but it seems to me that P4461's view mostly applies to
floating-point-like types. The sign of 0, the handling of NaNs or infinity,
etc.
>> When writing P4188, I had much simpler, but also broader use-cases in
mind.
>
>
> Well, the case study and most of the argument focuses on floating-point
just because we have concrete examples of that in the standard, but I think
you could extend those issues to other types. For example, since
fixed-point types typically don't have infinity or NaN, you would need to
think about how overflow works for those. Perhaps it could be saturating,
perhaps it could be throwing, perhaps it could just be undefined behavior.
So ... how does overflow work for fixed-point customizations of
std::math::exp2 then?
>
>> My point of view is that math functions are not, and should not be,
super special.
>> `std::abs(float)` is super special because float is quite special, and
IEEE 754 has super special rules, but `std::abs(int)` could be implemented
as `(i<0?-i:i)`, nothing special.
>> So, to me, std::abs is not special in itself. Only specific overloads
are special.
>> And I think that this is important and helpful for teachability: this is
not black magic, just an abstraction. It may or may not be implemented
using intrinsics, but ultimately, it's made of the same stuff as the rest
of the code.
>> In this sense, to me, math function overloads should not be different
from operator overloads.
>
>
> Sure, in its most basic form, abs can just be (i < 0 ? -i : i), although
you should use std::fabs, in practice. That example suits your argument
because there is only one "special" form of abs.
>
> However, did you know that we have std::max, std::fmax, std::fmaximum,
and std::fmaximum_num, all with subtly different handling of NaNs and
negative zero? You really wouldn't want to use a naive abs function for
floating-point and wouldn't want a naive max function either. But if an
abstract "max" customization point is implemented cleverly, which of these
maximum functions should be backing it?
>
> You may not have a std::math::max customization point in your paper
(maybe you should though?), but the point is that you can quickly run into
situations where a high-level mathematical operation turns into four
distinct concrete functions. I also don't think this is specific to
floating-point, and you're supporting the infinite set of user-defined
types after all.
>
>>
>> The specifics of the contract of a function hold for a specific
signature of that function, and not across all definition of that function.
>> `std::lower_bound` for instance changes its performance contract based
on the type it receives.
>
>
> That is true, but the change in performance is predictable. You're going
to get linear searches if you pass forward iterators and a better algorithm
if you pass random access iterators. Crucially, you can still reason about
the interface to a degree, and you don't need to look at implementation
details. With your std::math::sqrt, you really have no idea what it does
unless you look at the input type and the source code of the customization.
>
>>
>> And of course, I'll end this argument by mentioning operators.
>> P4188 intends to allow the specialisation of math functions for custom
types exactly in the context where these types would also overload
operators.
>> And operators come with no guarantee of any kind (apart from precedence,
associativity, and operand counts).
>> If I define my own float type, nothing is stopping me from implementing
operator* by Newton's method.
>> So... should we also specify every operator? And if so, what do we do
about `std::string` `operator+`?
>>
>> To me it should be enough to specify the behaviour and contract for
native type and std-lib types.
>
>
> Sure, people can do any silly thing they want with operators. The
operator is merely a symbol with some vague connotation, and then people
can imbue that symbol with any meaning they want.
>
> If providing a symbol is all there is to the paper, I see little
motivation for standardization though. I think it induces discombobulation
if library authors are presented with this symbol and have almost absolute
freedom in giving it meaning.
>
>>
>> My second point is about the pragmatic side of "How would we enforce
these aspects?"
>> We could maybe write some recommendations, saying it'd be nice if for
types for which it applies, signaling NaNs were handled in such and such
way.
>> Or we could go as far as saying that if they don't, it's a
non-std-conformant implementation.
>> To me, this amounts at shaking a finger. I believe it would have very
little impact in practice, unless it is somehow enforced.
>
>
> There is a fair amount of precedent for these things in the standard. We
have named requirements such as ForwardIterator or MoveAssignable which
require library authors to not only make the expression well-formed but to
also make them do the right thing. If they don't, it's undefined behavior.
One could argue that it's not really enforced if it's unchecked UB, but at
least there is a clear answer as to what things do.
>
>>
>> Suppose I am writing a custom type. I overload my operators, then I
specialise the math functions I expect will be needed...
>> If I suddenly realise that, although it works as I want it to in the
use-cases I have planned for this custom-type, I can't make my
implementation std-conformant trivially... what are the chances I'll merely
write a comment about it, or add an assert in the function body? That would
very much depend on the scope I have planned for that custom type, I guess.
>>
>> I think I understand the argument that if we lose all specification,
then the usage we can make of the type becomes more limited.
>> I would argue that, assuming we could test the compliance to these
aspects, maybe the test should be on the side of the generic code using
generic numeric types, rather than on the types themselves.
>> The reason for that being that different algorithms require different
aspect, and can be indifferent to other aspects.
>
>
> Users ultimately program against interfaces. If the generic interface
says "this returns the square root" without specifying any other details,
that's all you have to work with.
>
> This is going in a fairly unusual inverse direction where the user
chisels away at the details of the operation.
>
>>
>> Regarding the introduction of `fast_sqrt` and `cr_sqrt`, I believe they
should be the object of a different proposal, as they could live alongside
`std::sqrt`.
>> I can't help mentioning that I personally dislike this proposal, as I am
concerned with the scalability of this approach. The provided example
already suggests the possible addition of a `cr_fast_sqrt` which would
sacrifice precision to speed while remaining correctly rounded.
>
>
> cr_fast_sqrt is an oxymoron; "correctly rounded" means that the result is
always the most precise, down to the last bit. You cannot sacrifice any
precision to speed and remain correctly rounded.
>
>>
>> But I'd rather save this debate for a later time. I think I understand
the argument that if we added functions with a narrower contract later, how
would they interact with other custom types.
>> I believe that C++ has many contracts that are expressed as "equivalent
or narrower" instead of strictly narrower to avoid such problems.
>>
>>
>> Anyway, my suggestion would be to have traits.
>> If we don't want to (and can't, really) constraint the implementation,
but wish to offer a way to know the specific aspects of the implementation,
we could offer traits that can be opted-in to state the contract.
>> Custom types that merely wrap a native type, or are limited to internal
well-scoped usage where it doesn't matter could be implemented quickly,
while types meant for publication in published libraries would carefully
document their contract.
>> Generic code would then be able to chose whether they require specific
aspects.
>> Would that make sense?
>
>
> So if I understand it correctly, for example, you would have a trait that
lets you figure out whether a std::math::sqrt customization is correctly
rounded rather than having a correctly rounded std::math::cr_srt. There are
a few problems with this:
>
> You only get a single function, so the user doesn't get to choose between
a correctly rounded and an imprecise version. They can only tell which one
they're working with, but what happens when they don't like what they see?
Generic code could at best static_assert that the operation has the right
characteristics, but there is only one sqrt.
> This is kind of like a std::numeric_limits on a per-operation basis. It
seems fairly high-effort to maintain something like this, and it may be
tricky for us to extend it later, depending on the design.
>
> Anyway, all of this seems technically possible, but it seems both hard to
use and hard to customize when you are interested in additional details. As
a user, you need a bunch of assertions to make sure the operation fits your
use case, and as a library author, you need an extra trait providing
possibly a long list of details about your operation.
>
> It also doesn't solve the key problem pointed out in my paper that
library authors don't know what to do and don't know what to maximize for
(speed/correctness/C-compatibility). These traits would merely document
choices but don't help with making right choices.
>
>> Again, please don't take that as "the Truth according to SGL". I am very
aware of the limits of my understanding of the matter I'm approaching, and
for that reason I am very grateful for the attention and effort you have
put into your response paper and our conversations.
>
>
> If everyone in the committee was as humble and nice as you, C++26 would
have shipped already.
>
>
> Yours
> Jan
>
> _______________________________________________
> Sci mailing list
> Sci_at_[hidden]
> Subscription: https://lists.isocpp.org/mailman/listinfo.cgi/sci
> Link to this post: http://lists.isocpp.org/sci/2026/11/0930.php
_______________________________________________
Sci mailing list
Sci_at_[hidden]
Subscription: https://lists.isocpp.org/mailman/listinfo.cgi/sci
Link to this post: http://lists.isocpp.org/sci/2026/11/0931.php
Received on 2026-11-09 03:22:20
