Date: Mon, 18 Nov 2024 11:01:33 -0500
On Mon, Nov 18, 2024 at 10:23 AM Robin Savonen Söderholm via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> Hi!
>
> In certain embedded situations, you may want to avoid needless heap
> allocations. Since the major compiler makers apply "small object
> optimisation", it would be nice to somehow query if a type satisfies the
> SOO or if std::function would do a heap allocation (something that we could
> maybe static_assert in the very least if no other options are automatically
> available).
> I believe that whether an object satisfies SOO or not may depend on both
> size and alignment of the type, so the most straight-forward API I can
> think of is something along the lines of
> `std::function_is_inplace_v<FUNCTION, TYPE>` or something like that.
>
It can also depend on the trivial copyability (or trivial relocatability)
of the type, the nothrow move-constructibility, etc. etc., depending on the
vendor.
Nit: When we say "the type," we mean "the decayed type," e.g. if TYPE is a
function type, we really care about the function *pointer* type, not the
function type.
using F = std::function<int(int)>;
The trait you're looking for would have to be a *member* of `F`, not some
sort of global trait unrelated to `F`.
It feels to me like there are several possible designs (type trait?
constexpr function? member function of a constructed `F`?) but the simplest
is a type trait:
template<class T>
F wrap_in_function(T&& t) {
static_assert(F::uses_inplace_storage<T>::value);
return F(std::forward<T>(t));
}
I think the biggest objections to such a function are minor but
easy-to-stall-a-WG21-proposal:
(1) Naming is hard. It's easier now that we have the adjective "inplace"
(as in "inplace_vector"), but notice the vast difference between your
suggested name and mine. Should the standard library types provide both
`F::bikeshed<T>::value` and `F::bikeshed_v<T>`? What about user-provided
`F`s — do they need to provide *both* names?
(2) By exposing this information to the programmer, you're basically baking
it into the ABI more than it used to be. A lot of type-erasure schemes
continue to work at the ABI level even if the internal small-storage
criterion changes over time; but if you actually expose the criterion to
the user, then the *user* might start making ABI/API-relevant decisions
based on that criterion, and then the vendor can't change their criterion
without breaking the user's code. Of course this is already the case if the
user is *implicitly* relying on the vendor's criterion in order to achieve
correctness at runtime (e.g. assuming that a certain construction won't
heap-allocate and therefore won't block), but WG21 has a lot more tolerance
for breaking runtime behavior than breaking actual compilation. (A form of the
quantitative fallacy <https://en.wikipedia.org/wiki/McNamara_fallacy>: it's
a lot easier to measure "broken builds" than "subtly broken runtime
behavior" and so the former gets a lot more attention than the latter.)
> It would also be nice to have a "std::inplace_function" that is a stand-in
> replacement for std::function when it is unfit for similar reasons. I see a
> "SG14" inplace_function, but I am unsure if inplace_function has been up
> for discussion before and shot down for some reason.
>
I don't think it's ever been up for discussion. Carl Cook started work on a
proposal D0419
<https://github.com/WG21-SG14/SG14/blob/f5b0b24/Docs/Proposals/NonAllocatingStandardFunction.pdf>
in 2016, but never completed it and it was never put into a mailing.
The current version of sg14::inplace_function is here:
https://github.com/Quuxplusone/SG14/?tab=readme-ov-file#in-place-type-erased-types-future--c14
IMHO inplace_function *shouldn't* be standardized (and neither should
std::move_only_function or std::copyable_function have been standardized)
because there are simply too many knobs to fiddle with, and the correct
knob-settings are known only to the programmer of each individual codebase.
It is a fool's errand to try to standardize each possible setting of the
knobs, each under a different name. (immovable_function, nonnull_function,
non_implicitly_void_converting_function, etc. etc.) And we know this not
just intuitively but also empirically: the STL already picked the "wrong"
choice in several of these dimensions, several times in a row.
https://quuxplusone.github.io/blog/2019/03/27/design-space-for-std-function/
Data point: AFAICT, boost::function does not support anything like
`F::uses_inplace_storage<T>`.
HTH,
Arthur
std-proposals_at_[hidden]> wrote:
> Hi!
>
> In certain embedded situations, you may want to avoid needless heap
> allocations. Since the major compiler makers apply "small object
> optimisation", it would be nice to somehow query if a type satisfies the
> SOO or if std::function would do a heap allocation (something that we could
> maybe static_assert in the very least if no other options are automatically
> available).
> I believe that whether an object satisfies SOO or not may depend on both
> size and alignment of the type, so the most straight-forward API I can
> think of is something along the lines of
> `std::function_is_inplace_v<FUNCTION, TYPE>` or something like that.
>
It can also depend on the trivial copyability (or trivial relocatability)
of the type, the nothrow move-constructibility, etc. etc., depending on the
vendor.
Nit: When we say "the type," we mean "the decayed type," e.g. if TYPE is a
function type, we really care about the function *pointer* type, not the
function type.
using F = std::function<int(int)>;
The trait you're looking for would have to be a *member* of `F`, not some
sort of global trait unrelated to `F`.
It feels to me like there are several possible designs (type trait?
constexpr function? member function of a constructed `F`?) but the simplest
is a type trait:
template<class T>
F wrap_in_function(T&& t) {
static_assert(F::uses_inplace_storage<T>::value);
return F(std::forward<T>(t));
}
I think the biggest objections to such a function are minor but
easy-to-stall-a-WG21-proposal:
(1) Naming is hard. It's easier now that we have the adjective "inplace"
(as in "inplace_vector"), but notice the vast difference between your
suggested name and mine. Should the standard library types provide both
`F::bikeshed<T>::value` and `F::bikeshed_v<T>`? What about user-provided
`F`s — do they need to provide *both* names?
(2) By exposing this information to the programmer, you're basically baking
it into the ABI more than it used to be. A lot of type-erasure schemes
continue to work at the ABI level even if the internal small-storage
criterion changes over time; but if you actually expose the criterion to
the user, then the *user* might start making ABI/API-relevant decisions
based on that criterion, and then the vendor can't change their criterion
without breaking the user's code. Of course this is already the case if the
user is *implicitly* relying on the vendor's criterion in order to achieve
correctness at runtime (e.g. assuming that a certain construction won't
heap-allocate and therefore won't block), but WG21 has a lot more tolerance
for breaking runtime behavior than breaking actual compilation. (A form of the
quantitative fallacy <https://en.wikipedia.org/wiki/McNamara_fallacy>: it's
a lot easier to measure "broken builds" than "subtly broken runtime
behavior" and so the former gets a lot more attention than the latter.)
> It would also be nice to have a "std::inplace_function" that is a stand-in
> replacement for std::function when it is unfit for similar reasons. I see a
> "SG14" inplace_function, but I am unsure if inplace_function has been up
> for discussion before and shot down for some reason.
>
I don't think it's ever been up for discussion. Carl Cook started work on a
proposal D0419
<https://github.com/WG21-SG14/SG14/blob/f5b0b24/Docs/Proposals/NonAllocatingStandardFunction.pdf>
in 2016, but never completed it and it was never put into a mailing.
The current version of sg14::inplace_function is here:
https://github.com/Quuxplusone/SG14/?tab=readme-ov-file#in-place-type-erased-types-future--c14
IMHO inplace_function *shouldn't* be standardized (and neither should
std::move_only_function or std::copyable_function have been standardized)
because there are simply too many knobs to fiddle with, and the correct
knob-settings are known only to the programmer of each individual codebase.
It is a fool's errand to try to standardize each possible setting of the
knobs, each under a different name. (immovable_function, nonnull_function,
non_implicitly_void_converting_function, etc. etc.) And we know this not
just intuitively but also empirically: the STL already picked the "wrong"
choice in several of these dimensions, several times in a row.
https://quuxplusone.github.io/blog/2019/03/27/design-space-for-std-function/
Data point: AFAICT, boost::function does not support anything like
`F::uses_inplace_storage<T>`.
HTH,
Arthur
Received on 2024-11-18 16:01:50