On Thu, Oct 6, 2022 at 8:44 AM Jonathan Wakely <cxx@kayari.org> wrote:
On Thu, 6 Oct 2022 at 12:47, Arthur O'Dwyer <arthur.j.odwyer@gmail.com> 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