Consider this Swift struct:
public struct Example
{
public func foo(callback: ()->Void)
{
....
}
public func blah(i: Int)
{
....
}
....
}
Using Swift/C++ interop, I can create Example
objects and call methods like blah
. But I can't call foo
because Swift/C++ interop doesn't currently support passing closures (right?).
On the other hand, Swift/objC does support passing objC blocks to Swift functions. But I can't use that here because Example is a Swift struct, not a class. So I could change it to a class, and update everything to work with reference rather than value semantics; but then I also have to change the objC++ code to create the object and call its methods using objC syntax. I'd like to avoid that.
Is there some hack that I can use to make this possible? I'm hoping that I can wrap a C++ std::function
in some sort of opaque wrapper and pass that to swift, or something.
Thanks for any suggestions!
I'm hoping that I can wrap a C++ std::function in some sort of opaque wrapper and pass that to swift, or something.
Here that is, if anyone cares:
// swiftcompat_voidfunc.hh
#include <concepts>
// swiftcompat_voidfunc is like a std::function<void(void)>, but it can be used with
// Swift/C++ interop.
// In Swift, arange for this header to be visible to Swift (i.e. include it from your
// bridging header) and declare parameters and variables of type swiftcompat_voidfunc.
// They have value semantics. Invoke the function using ().
// In C++, include the other header, swiftcompat_voidfunc_cpp.hh. Construct swiftcompat_voidfunc
// objects, passing the invokable (e.g. a lambda) to store. Pass these objects (by value) to
// Swift APIs.
// The issues that we need to address to make this work are:
// - We can't pass std::function<> to Swift.
// - We can't call a Swift closure type from C++.
// - (We can pass objC blocks to Swift using Swift/objC interop, but we don't want to do that.)
// - We can't expose std::unique_ptr<> in the bridged code (hence a raw pointer pimpl).
// - The type needs to be copyable, not just moveable.
// If anything changes in Swift/C++ interop that changes those assumptions, we may not need this.
class swiftcompat_voidfunc
{
struct Impl;
Impl* pimpl;
public:
swiftcompat_voidfunc(std::invocable<> auto cbk);
~swiftcompat_voidfunc();
swiftcompat_voidfunc(const swiftcompat_voidfunc& other);
swiftcompat_voidfunc& operator=(const swiftcompat_voidfunc& other);
void operator()();
};
// swiftcompat_voidfunc_cpp.hh
#include "swiftcompat_voidfunc.hh"
#include <functional>
// See comments in swiftcompat_voidfunc.hh.
// Include this file from C++ code.
struct swiftcompat_voidfunc::Impl
{
std::function<void(void)> fn;
};
swiftcompat_voidfunc::swiftcompat_voidfunc(std::invocable<> auto fn):
pimpl(new Impl(fn))
{}
// swiftcompat_voidfunc.cc
#include "swiftcompat_voidfunc_cpp.hh"
swiftcompat_voidfunc::~swiftcompat_voidfunc()
{
delete pimpl;
}
swiftcompat_voidfunc::swiftcompat_voidfunc(const swiftcompat_voidfunc& other):
pimpl(new Impl(*other.pimpl))
{}
swiftcompat_voidfunc& swiftcompat_voidfunc::operator=(const swiftcompat_voidfunc& other)
{
delete pimpl;
pimpl = new Impl(*other.pimpl);
return *this;
}
void swiftcompat_voidfunc::operator()()
{
pimpl->fn();
}