C++ Logo

sg14

Advanced search

Re: Interesting idea for non-throwing std::function

From: Jonathan Wakely <cxx_at_[hidden]>
Date: Tue, 4 Oct 2022 00:04:57 +0100
On Mon, 3 Oct 2022 at 23:15, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
wrote:

> On Mon, Oct 3, 2022 at 5:23 PM Patrice Roy via SG14 <sg14_at_[hidden]>
> wrote:
>
>> What about making the constructor conditionally noexcept? The
>>> implementation knows whether a given type will be stored on the heap, and
>>> if that type can be constructed without throwing, so can easily expose that.
>>>
>>> What people care about is a property of the constructor, so why not make
>>> it part of the constructor signature?
>>>
>>> You can static assert is_nothrow_constructible.
>>>
>>
>> If the SG14 contributors like this, we could turn it into a paper. It
>> would (IMO) have a good chance of being accepted. This would let people use
>> std::function without any risk of allocating in the cases where they care
>> about this, and at essentially no cost. And if someone thinks all
>> std::function cases in their codebase should be protected that way, they
>> could envision writing a make_function() factory that would do this at no
>> cost either, being essentially
>>
>> template <class F> std::function<F> make_function(F && f) {
>> static_assert(std::is_nothrow_constructible_v<std::function<F>>);
>> return { std::forward<F>(f) };
>> }
>>
>
> (1) You mean this:
>
> template<class Sig, class T>
> std::function<Sig> make_function(T&& t) noexcept {
> static_assert(noexcept( std::function<Sig>(std::forward<T>(t)) ));
> return std::function<Sig>(std::forward<T>(t));
> }
>
> (2) Notice that "The ctor doesn't throw" implies "The ctor doesn't
> allocate," but not vice versa. For example
> struct Empty {
> Empty() {}
> Empty(const Empty&) {}
> Empty& operator=(const Empty&) { return *this; }
> };
> auto lam = [e = Empty()](){};
> auto f = make_function<void()>(lam); // static-assert fails because
> copying `lam` is potentially throwing, regardless of whether it fits in the
> small buffer
> Therefore there are annoying false positives.
>
> (3) Not all std::function creation can be funneled through
> `make_function`. Consider
> void call_it(std::function<void()> g);
> auto f = make_function<int()>([x=42]() { return x; }); // OK
> call_it(f); // not OK; the converting constructor from
> function<int()> to function<void()> will allocate
> Therefore there are dangerous false negatives.
>
> (4) In implementing this idea for libc++ just now, I rediscovered
> something we already know: adding noexcept-specifications is really really
> error-prone. Libc++ will use the small buffer for any type T that is small
> enough and nothrow-copy-constructible. However, in the process of
> constructing the innards into the small buffer, libc++ happens to invoke
> T's *move constructor*. If T's copy constructor is non-throwing, but T's
> move constructor throws, then the exception will slam into
> function::function(T)'s `noexcept` firewall and terminate the program. This
> is non-conforming. I fixed this by saying, "Okay, libc++ no longer stores T
> in the small buffer unless T is nothrow-copy-constructible *and*
> nothrow-move-constructible"... but that's no basis for a system of
> government.
>

Changing the condition for using the buffer affects ABI. You can just make
the noexcept-specifier false for this case instead. It would use the
buffer, but doing so might throw. So constructing the std::function can
throw.

Yes, that means sometimes is_nothrow_constructible is false even though it
doesn't use the buffer ... for types that have non-throwing copies but
throwing moves ... i.e. almost no types in the real world.



>
> I think it would be reasonable for a vendor (such as libstdc++) to add
> conditional-noexcept to std::function's converting constructor. But it
> isn't sufficient to get you what you want, and it is very likely to
> introduce subtle corner-case bugs into the vendor's implementation IMHO.
> Therefore I *don't* think it would be good to *force* vendors to do it.
>
> I intend to get the libc++ patch working and make it available on my fork
> https://p1144.godbolt.org/z/hja8sdT6b
> hopefully by the end of the week (yet not within the next couple of
> hours). Then people could (theoretically) play around with it and see
> whether it suffices for them. I very much bet that it won't.
>
> –Arthur
>

Received on 2022-10-03 23:05:10