C++ Logo

sg14

Advanced search

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

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Thu, 6 Oct 2022 19:10:26 -0400
On Thu, Oct 6, 2022 at 8:44 AM Jonathan Wakely <cxx_at_[hidden]> wrote:

> On Thu, 6 Oct 2022 at 12:47, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
> wrote:
>
>> Jonathan Wakely wrote:
>> > Changing the condition for using the buffer affects ABI.
>>
>> Only in the very narrow sense that having two slightly different versions
>> of `function` floating around the source code technically violates the ODR.
>> This was just a change in the condition under which *the constructor
>> puts* a T into the buffer; once the T is in the buffer (or allocated on
>> the heap), everything is still done through the same set of type-erased
>> operations as before. A program might end up with one `function` holding a
>> `Widget` in its buffer, and another `function` holding a `Widget` on the
>> heap, but since the fact that they're even `Widget`s at all has been
>> type-erased, that's actually totally fine.
>>
>
> I'm not familiar with the libc++ implementation, but for the libstdc++ one
> it wouldn't work. The internals of std::function are a class template
> instantiated with the type of the stored callable, and so whether it's
> stored locally or not is a property of the specialization. If you change
> the definition of the specialization, it's a real "this mangled name has
> different behaviour now" ABI-breaking ODR violation.
>

Ah, I haven't checked libstdc++'s code, but I suppose I see how that would
be. I'm used to thinking of the behaviors of a vendor-quality type-erased
thing being stored in essentially
    void (*behavior_)(void *data, void *otherInputs);
    data_ = &t; // or whatever
    behavior_ = +[](void*, void*) { ~~~~ };
    behavior_(data_, otherInputs);
i.e., "to find out what this erased thing does, call the function pointer
stored *here* (with the data stored *here*)," and the function pointer is
just pointing to some anonymous piece of code known only to the constructor
of `function` (which is a template and which I hope gets inlined). But if
it's instead more like
    unique_ptr<BehaviorBase> p_;
    p_ = make_unique<BehaveLike<T>>(&t);
    p_->behave(otherInputs);
then it's "to find out what this erased thing does, call the function
pointer stored in this vtable *here* (with this data)," and that vtable is
not set up by the same constructor template of `function`, it's actually
named [vtable of BehaveLike<Widget>] and if multiple object files disagree
about the contents of [vtable of BehaveLike<Widget>] then we have a serious
ODR problem. Right. (IIUC.)

Do you see a serious/reproducible ODR problem lurking even in an
implementation like this move_only_function?
https://github.com/Quuxplusone/llvm-project/commit/2ce12c87912d8e9ba83289d4fc1583dcd9699751#diff-43f982a76f2863a492f9f7578dd65ddaa78c54085bbcc4c4b43ab81bf18df751R148-R182
Hmm, I guess that if `__construct`-or-one-of-its-callers is not inlined,
then this has the same kind of problem? But in that case I don't really see
any way around it. Changing the behavior of *any* C++ function is an (ODR
violation / ABI break) unless we can prove that that function's linker
symbol is optimized away, right?

----
It looks like libc++ decides whether the object is stored locally by
> comparing a pointer to its own buffer, so once that's set in the ctor, any
> code looking at the object will know where the callable is stored. I wonder
> if I can safely migrate libstdc++ to something like that.
>
FWIW, personally, I strongly advise against changing libstdc++'s `function`
to store a "pointer into self," because that would destroy
libstdc++'s `function`'s trivial relocatability.
Neither libc++ nor Microsoft have a trivially relocatable `function` today;
but I'd certainly rather they join you, than you join them. ;)
https://quuxplusone.github.io/blog/2019/02/20/p1144-what-types-are-relocatable/
I did this now. The down side, of course, is that this makes the questions
>> "Does this Widget fit in the small buffer?" and
>> "is_nothrow_convertible_v<Widget, function>?" give different answers,
>>
>
> Yes but only for types with non-throwing copies and throwing moves ... are
> those types realistic?
>
No. :)
*Although this is a minor issue compared to the gaping hole that is (#3)
>> below.*
>>
>
–Arthur

Received on 2022-10-06 23:10:38