Swift/objC combined with Swift/C++ interop

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!

Answered by endecotp in 819813022

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();
}
Accepted Answer

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();
}
Swift/objC combined with Swift/C++ interop
 
 
Q