I hoped that we could wait longer to think about this, but unfortunately, the time is now. It is hard to continue to make progress with C++ interop while we have such a glaring issue in front of us.
Essentially, the problem is that we currently don't import any members of base classes. This includes special members, meaning we get linker errors when trying to use a class with a base class that has special members (such as a custom copy constructor or destructor). You can see an example of this if you try to use std::vector
in Swift code.
The best solution for this problem should be clear: import both the parent and child classes, then have the child inherit from the parent. Unfortunately, this is not so simple. C++ classes are always imported as Swift structs which currently do not support inheritance from other Swift structs. This would be a big change for the language. One that we should arguably make, but, regardless, will take time to discuss and even more time to implement. We cannot have C++ interop be blocked on this, so we need to look for other solutions, at least for the time being.
Another solution would be to use protocols with default implementations to provide parent classes and their members. During IRGen, these protocols could be magically turned into concrete types, like structs. This would be a hack and would take some work, so I'm not too excited about it. But if there is interest, I can elaborate on my thinking a bit more.
Another solution is to flatten child classes into a single class. All the parent members would then be added to the child class, and it would appear as though there was no parent. I like this solution a lot. It seems like the easiest way to get something workable, and from a user's point of view, there wouldn't be much difference between this and "the best solution."
Here's an example of what importing a parent and child class would look like:
// C++
struct A {
void test1();
};
struct B : A {
void test2();
};
// Imported as
struct A {
func test1()
}
struct B {
func test1()
func test2()
}
The biggest problem here is the Swift compiler would view child and parent classes as completely different, unrelated types. This means it would not be easy to up/down cast instances of these classes. However, I think there may be a fairly simple solution to this as well. When importing these types, it would not be too hard to add some convenience methods to help with casting. For example:
// C++
struct A {
void test1();
};
struct B : A {
void test2();
};
// Imported as
struct A {
func test1()
func asB() -> B?
}
struct B {
func test1()
func test2()
func asA() -> A // alternatively "asParent()"
}
If anyone has thoughts about these possible implementations or has an idea for a better implementation, I'd love to hear them.