Exposing Swift protocols to C++ as pure abstract classes

Hello, everyone.

As I explore Swift C++ interoperability, I noticed that Swift protocols are not exposed to C++. Swift protocol is a very important way to abstract and constrain types, but that is currently invisible from C++ through C++ interoperability.

Let’s consider the following example. These are defined in Swift.

protocol Animal {
  func greet()
}

struct Dog: Animal {
  func greet() {
    print("bow wow")
  }
}
struct Cat: Animal {
  func greet() {
    print("meow")
  }
}

In Swift, abstracting these types are very easy ike this.

let animals: [any Animal] = [Dog(), Cat()]
animals.forEach { $0.greet() } // "bow wow", "meow"

However, in the current C++ interoperability, Dog and Cat are exposed as the completely irrelevant types. Since C++ does not have a feature like conforming to a protocol with Swift extension, it's hard for C++ to abstract these two types in the C++ layer.
If the protocol Animal is exposed to C++ like this,

class Animal {
  virtual void greet() = 0;
}

The concrete types can be exposed to C++ like this. And the abstraction will be easy too.

class Dog: public Animal { ... }
class Cat: public Animal { ... }

std::vector<Animals> animals = { Dog::init(), Cat::init() };

It seems to be a reasonable approach to expose a Swift protocol as a pure abstract class of C++, since the role of these two are quite similar. Has this topic been considered before?

FYI: C++ Abstract Class Inheritance and C++-Interop (to Swift Protocols) discussed the opposite direction exposure, exposing a C++ abstract class to Swift as a protocol.

Just as a warning, this isn't safe in C++, as if Dog and Cat have any stored properties they'll be dropped by this (object slicing). You would need to store a std::vector<Animal*> for it to be safe.

Would we have to add a template<class T> struct SwiftExistential to match Swift's expected layout for any Animal? Or would the compiler generate thunks to convert between Swift any Animal and C++ Animal*?

2 Likes

For the existential type in C++, I think adding SwiftExistential type is reasonable, especially when we consider the case that one type which conforms the protocol is a struct and another is a class. In case of Swift class, the type is exposed to C++ as a kind of smart pointer like std::shared_ptr<MyClassType>, and it would be strange to handle this type as a raw pointer.