I was following the official guide for cpp interop and some topics including How can I use the pimpl idiom with std::unique_ptr with Swift/C++ interop?
I'll start with several samples that I tried to make it work.
I was experimenting a bit with c++ interop trying to compile and use proprietary pre-compiled library which has a lot of c++ pimpl idioms in its headers.
Pimpl automatically disables a lot of things from automatic bridging with note:
note: record '<xxx>' is not automatically available: does not have a copy constructor or destructor; does this type have reference semantics?
I was playing around it with SWIFT_NONCOPYABLE
and SWIFT_UNSAFE_REFERENCE
and found some things that seems a bit strange to me.
For example, I have approximately the following classes structure:
class Class1 {
public:
Class1(long);
public:
virtual ~Class1();
};
class Class2: Class1 {
public:
Class2(); // cannot compile without this constructor when marking SWIFT_NONCOPYABLE
Class2(const Class2 &) = delete;
Class2(int);
virtual ~Class2();
class Private;
private:
std::unique_ptr<Private> m_value;
}
I tried to use SWIFT_NONCOPYABLE
directly applying to Class2
:
class Class2: Class1 {
...
} SWIFT_NONCOPYABLE;
That did not help with the following outcome:
record 'Class2' is not automatically available: does not have a copy constructor or destructor; does this type have reference semantics
Other attempt
Placing SWIFT_NONCOPYABLE
as was discussed in https://forums.swift.org/t/how-can-i-use-the-pimpl-idiom-with-std-unique-ptr-with-swift-c-interop/76606/5:
class SWIFT_NONCOPYABLE Class2: Class1 {
...
};
led to compilation error:
Sources/CppTarget/Headers/LibCpp/Test.h:15:31: error: expected ';' after top level declarator
15 | class SWIFT_NONCOPYABLE Class2: Class1 {
| ^
| ;
I tried other two ways.
First attempt:
struct WrapClass2_1 {
WrapClass2_1(); // cannot compile without this constructor when marking SWIFT_NONCOPYABLE
WrapClass2_1(Class2 && value);
WrapClass2_1(int v);
private:
Class2 m_value;
} SWIFT_NONCOPYABLE;
The same outcome:
note: record 'WrapClass2_1' is not automatically available: does not have a copy constructor or destructor; does this type have reference semantics?
Successful attempt: use Pimpl with c++ struct over the class with pimpl:
struct WrapClass2_2 {
WrapClass2_2(); // cannot compile without this constructor when marking SWIFT_NONCOPYABLE
WrapClass2_2(Class2 && value);
WrapClass2_2(int v);
private:
std::unique_ptr<Class2> m_value;
} SWIFT_NONCOPYABLE;
That one worked:
swift code
final class A { // wrap non-copyable
let aa = Foo.WrapClass2_2(123)
init() {}
}
print(A())
$ swift run
Building for debugging...
[7/7] Linking CppTargetTest
Build of product 'CppTargetTest' complete! (0.74s)
default ctor() called: why?
123
CppTargetTest.A
But not that good in fact
If add WrapClass2_2(const Class2 & value) = delete;
everything will broke again...
So, I can wrap all the classes with struct pimpl over class pimpl and that will work.
There are some questions left that I didn't find answers for:
- Is it intentional to disallow c++ class (which is equal to swift struct) to be non-copyable?
- Would it be technically possible to make non-copyable but movable types make non-copyable in swift?
- Why should default constructor (without arguments) is required when adding
SWIFT_NONCOPYABLE
? and... - Why is default constructor called when using non-default constructor?
Thanks in advance for helping with this topic.
My experiments with minimal sources are available at: external-reproducers/swift/swift-cpp-interop-non-copyable at main · ordo-one/external-reproducers · GitHub