C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Query "SOO" from std::function and/or std::inplace_function

From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
Date: Tue, 19 Nov 2024 09:52:13 +0100
wt., 19 lis 2024 o 06:44 Robin Savonen Söderholm
<robinsavonensoderholm_at_[hidden]> napisał(a):
>
> Try to implement that before proposing it, as you would essentially have to implement all versions of the func at once and then disable everything that should not be enabled. Tricky to get completely right.
>
> // Robin
>

This is only partially true, remember that we have specializations?
This means some implementation can look like:

```
template<typename Func>
class base_func<Func, func_clasic> : std::function<Func>
{
};
```

And this can be fully independent from the rest of implementation. And
this is C++98 tool.
As far as I know adding new `require` based on new unique type should
be ABI compatible (but probably bend ODR a bit).
This means we could always "fork" implementation to add new features like:

```
template<typename Func, typename Config> require !haveSpecialType<Config>
class base_func<Func, Config>
{
   // old
};
template<typename Func, typename Config> require haveSpecialType<Config>
class base_func<Func, Config>
{
   // new
};
```

Alternatively if we could guarantee ordering then we could even keep
the base template `require` free.

Overall this will be effective "umbrella type" that will contains lot
of unique types with same name and
allow writing some generic code in some cases without assuming
anything about callback:

```
template<typename Config>
void repeat(base_func<void(), Config> f)
{
    f();
    f();
    f();
}
```

>
> On Mon, Nov 18, 2024, 22:41 Marcin Jaczewski via Std-Proposals <std-proposals_at_[hidden]> wrote:
>>
>> pon., 18 lis 2024 o 17:02 Arthur O'Dwyer via Std-Proposals
>> <std-proposals_at_[hidden]> napisał(a):
>> >
>> > 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: 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 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/
>>
>> What if we make all these configs exposed as one parameter type?
>> Image template:
>>
>> ```
>> template<typename Func, typename Config = func_def>
>> class base_func;
>>
>> base_func<int(), func_non_move> foo1;
>> base_func<int(), sbo<16, 8>> foo2;
>> base_func<int(), custom<sbo<16, 8>, func_move, func_copy>> foo3;
>> base_func<int(), my_own_tag<10>> foo4;
>> ```
>>
>> And function implementation will querry some data from this config, we
>> could define too who conversion
>> work between diffrent types.
>> If the committee forgets some "dimension" it can add new tags later
>> that expose this to users.
>> Could this work better?
>>
>>
>>
>>
>> >
>> > Data point: AFAICT, boost::function does not support anything like `F::uses_inplace_storage<T>`.
>> >
>> > HTH,
>> > Arthur
>> > --
>> > Std-Proposals mailing list
>> > Std-Proposals_at_[hidden]
>> > https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2024-11-19 08:52:28