www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - Who says we can't call C++ constructors?

reply Atila Neves <atila.neves gmail.com> writes:
 From https://dlang.org/spec/cpp_interface.html:

"C++ constructors, copy constructors, move constructors and 
destructors cannot be called directly in D code".

O RLY?

     // hdr.hpp
     struct Struct {
         void *data;
         Struct(int i);
         Struct(const Struct&);
         Struct(Struct&&);
         ~Struct();
         int number() const;
     };


     // cpp.cpp
     #include "hdr.hpp"
     #include <iostream>

     using namespace std;

     Struct::Struct(int i) {
         cout << "  C++:  int ctor " << i << endl;
         data = new int(i);
     }

     Struct::Struct(const Struct& other) {
         cout << "  C++: copy ctor " << other.number() << endl;
         data = new int(*reinterpret_cast<int*>(other.data));
     }

     Struct::Struct(Struct&& other) {
         cout << "  C++: move ctor " << other.number() << endl;
         data = other.data;
         other.data = nullptr;
     }

     Struct::~Struct() {
         cout << "  C++ dtor " << number() << endl;
         delete reinterpret_cast<int*>(data);
     }

     int Struct::number() const {
         return data == nullptr ? 0 : 
*reinterpret_cast<int*>(data);
     }


     // ctors.dpp
     #include "hdr.hpp"
     import std.stdio;

     void main() {
         writeln("D: int ctor");
         const cs = const Struct(2);
         auto  ms = Struct(3);
         writeln;

         writeln("D: copy ctor");
         auto ccs = Struct(cs); assert(ccs.number() == 2);
         auto cms = Struct(ms); assert(cms.number() == 3);
         writeln;

         writeln("D: move ctor");
         auto tmp = Struct(4);
         // dpp.move causes the move ctor be called instead of the 
copy ctor
         auto mv1 = Struct(dpp.move(tmp)); assert(mv1.number() == 
4);
         // moved from, now T.init (even if the C++ code doesn't 
do that)
         assert(tmp.data is null);

         // This last line doesn't work with dmd due to issue 
18784.
         // It works fine with ldc though.
         auto mv2 = Struct(Struct(5)); assert(mv2.number() == 5);
         writeln;
     }


% clang++ -c cpp.cpp
% d++ --compiler=ldc2 ctors.dpp cpp.o -L-lstdc++
% ./ctors

D: int ctor
   C++:  int ctor 2
   C++:  int ctor 3

D: copy ctor
   C++: copy ctor 2
   C++: copy ctor 3

D: move ctor
   C++:  int ctor 4
   C++: move ctor 4
   C++ dtor 0
   C++:  int ctor 5
   C++: move ctor 5
   C++ dtor 0

   C++ dtor 5
   C++ dtor 4
   C++ dtor 0
   C++ dtor 3
   C++ dtor 2
   C++ dtor 3
   C++ dtor 2



Atila
Apr 21
next sibling parent 12345swordy <alexanderheistermann gmail.com> writes:
On Saturday, 21 April 2018 at 12:41:02 UTC, Atila Neves wrote:
 From https://dlang.org/spec/cpp_interface.html:

 "C++ constructors, copy constructors, move constructors and 
 destructors cannot be called directly in D code".

 O RLY?

     // hdr.hpp
     struct Struct {
         void *data;
         Struct(int i);
         Struct(const Struct&);
         Struct(Struct&&);
         ~Struct();
         int number() const;
     };


     // cpp.cpp
     #include "hdr.hpp"
     #include <iostream>

     using namespace std;

     Struct::Struct(int i) {
         cout << "  C++:  int ctor " << i << endl;
         data = new int(i);
     }

     Struct::Struct(const Struct& other) {
         cout << "  C++: copy ctor " << other.number() << endl;
         data = new int(*reinterpret_cast<int*>(other.data));
     }

     Struct::Struct(Struct&& other) {
         cout << "  C++: move ctor " << other.number() << endl;
         data = other.data;
         other.data = nullptr;
     }

     Struct::~Struct() {
         cout << "  C++ dtor " << number() << endl;
         delete reinterpret_cast<int*>(data);
     }

     int Struct::number() const {
         return data == nullptr ? 0 : 
 *reinterpret_cast<int*>(data);
     }


     // ctors.dpp
     #include "hdr.hpp"
     import std.stdio;

     void main() {
         writeln("D: int ctor");
         const cs = const Struct(2);
         auto  ms = Struct(3);
         writeln;

         writeln("D: copy ctor");
         auto ccs = Struct(cs); assert(ccs.number() == 2);
         auto cms = Struct(ms); assert(cms.number() == 3);
         writeln;

         writeln("D: move ctor");
         auto tmp = Struct(4);
         // dpp.move causes the move ctor be called instead of 
 the copy ctor
         auto mv1 = Struct(dpp.move(tmp)); assert(mv1.number() 
 == 4);
         // moved from, now T.init (even if the C++ code doesn't 
 do that)
         assert(tmp.data is null);

         // This last line doesn't work with dmd due to issue 
 18784.
         // It works fine with ldc though.
         auto mv2 = Struct(Struct(5)); assert(mv2.number() == 5);
         writeln;
     }


 % clang++ -c cpp.cpp
 % d++ --compiler=ldc2 ctors.dpp cpp.o -L-lstdc++
 % ./ctors

 D: int ctor
   C++:  int ctor 2
   C++:  int ctor 3

 D: copy ctor
   C++: copy ctor 2
   C++: copy ctor 3

 D: move ctor
   C++:  int ctor 4
   C++: move ctor 4
   C++ dtor 0
   C++:  int ctor 5
   C++: move ctor 5
   C++ dtor 0

   C++ dtor 5
   C++ dtor 4
   C++ dtor 0
   C++ dtor 3
   C++ dtor 2
   C++ dtor 3
   C++ dtor 2



 Atila
You are a mad man!
Apr 21
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
On 21 April 2018 at 05:41, Atila Neves via Digitalmars-d-announce
<digitalmars-d-announce puremagic.com> wrote:
 From https://dlang.org/spec/cpp_interface.html:

 "C++ constructors, copy constructors, move constructors and destructors
 cannot be called directly in D code".

 O RLY?

     // hdr.hpp
     struct Struct {
         void *data;
         Struct(int i);
         Struct(const Struct&);
         Struct(Struct&&);
         ~Struct();
         int number() const;
     };


     // cpp.cpp
     #include "hdr.hpp"
     #include <iostream>

     using namespace std;

     Struct::Struct(int i) {
         cout << "  C++:  int ctor " << i << endl;
         data = new int(i);
     }

     Struct::Struct(const Struct& other) {
         cout << "  C++: copy ctor " << other.number() << endl;
         data = new int(*reinterpret_cast<int*>(other.data));
     }

     Struct::Struct(Struct&& other) {
         cout << "  C++: move ctor " << other.number() << endl;
         data = other.data;
         other.data = nullptr;
     }

     Struct::~Struct() {
         cout << "  C++ dtor " << number() << endl;
         delete reinterpret_cast<int*>(data);
     }

     int Struct::number() const {
         return data == nullptr ? 0 : *reinterpret_cast<int*>(data);
     }


     // ctors.dpp
     #include "hdr.hpp"
     import std.stdio;

     void main() {
         writeln("D: int ctor");
         const cs = const Struct(2);
         auto  ms = Struct(3);
         writeln;

         writeln("D: copy ctor");
         auto ccs = Struct(cs); assert(ccs.number() == 2);
         auto cms = Struct(ms); assert(cms.number() == 3);
         writeln;

         writeln("D: move ctor");
         auto tmp = Struct(4);
         // dpp.move causes the move ctor be called instead of the copy ctor
         auto mv1 = Struct(dpp.move(tmp)); assert(mv1.number() == 4);
         // moved from, now T.init (even if the C++ code doesn't do that)
         assert(tmp.data is null);

         // This last line doesn't work with dmd due to issue 18784.
         // It works fine with ldc though.
         auto mv2 = Struct(Struct(5)); assert(mv2.number() == 5);
         writeln;
     }


 % clang++ -c cpp.cpp
 % d++ --compiler=ldc2 ctors.dpp cpp.o -L-lstdc++
 % ./ctors

 D: int ctor
   C++:  int ctor 2
   C++:  int ctor 3

 D: copy ctor
   C++: copy ctor 2
   C++: copy ctor 3

 D: move ctor
   C++:  int ctor 4
   C++: move ctor 4
   C++ dtor 0
   C++:  int ctor 5
   C++: move ctor 5
   C++ dtor 0

   C++ dtor 5
   C++ dtor 4
   C++ dtor 0
   C++ dtor 3
   C++ dtor 2
   C++ dtor 3
   C++ dtor 2



 Atila
Paste the pre-processed D code? Did you generate the C++ mangled symbol name and call it from a D wrapper? I've attempted that before in 'normal' D ;)
Apr 21
parent reply Atila Neves <atila.neves gmail.com> writes:
On Saturday, 21 April 2018 at 18:11:09 UTC, Manu wrote:
 On 21 April 2018 at 05:41, Atila Neves via 
 Digitalmars-d-announce <digitalmars-d-announce puremagic.com> 
 wrote:
[...]
Paste the pre-processed D code? Did you generate the C++ mangled symbol name and call it from a D wrapper? I've attempted that before in 'normal' D ;)
Mostly just constructors with `pragma(mangle)` but having move work correctly involved two wrappers, one taking by value and one taking by non-const ref.
Apr 23
parent reply Manu <turkeyman gmail.com> writes:
On 23 April 2018 at 07:27, Atila Neves via Digitalmars-d-announce
<digitalmars-d-announce puremagic.com> wrote:
 On Saturday, 21 April 2018 at 18:11:09 UTC, Manu wrote:
 On 21 April 2018 at 05:41, Atila Neves via Digitalmars-d-announce
 <digitalmars-d-announce puremagic.com> wrote:
 [...]
Paste the pre-processed D code? Did you generate the C++ mangled symbol name and call it from a D wrapper? I've attempted that before in 'normal' D ;)
Mostly just constructors with `pragma(mangle)` but having move work correctly involved two wrappers, one taking by value and one taking by non-const ref.
Can you explain the move issue? That's interesting.
Apr 23
parent reply Atila Neves <atila.neves gmail.com> writes:
On Monday, 23 April 2018 at 20:40:47 UTC, Manu wrote:
 On 23 April 2018 at 07:27, Atila Neves via 
 Digitalmars-d-announce <digitalmars-d-announce puremagic.com> 
 wrote:
 On Saturday, 21 April 2018 at 18:11:09 UTC, Manu wrote:
 On 21 April 2018 at 05:41, Atila Neves via 
 Digitalmars-d-announce <digitalmars-d-announce puremagic.com> 
 wrote:
 [...]
Paste the pre-processed D code? Did you generate the C++ mangled symbol name and call it from a D wrapper? I've attempted that before in 'normal' D ;)
Mostly just constructors with `pragma(mangle)` but having move work correctly involved two wrappers, one taking by value and one taking by non-const ref.
Can you explain the move issue? That's interesting.
Sure. I thought about generating D wrappers for everything, but in TDD fashion I tried slapping a pragma(mangle) on the copy constructor and things just worked. Odd then that dmd doesn't try to correctly mangle constructors and destructors since they're perfectly callable. But then there's the move constructor. There's no signature I can use for it exactly, so how do I hack that? Well, in D a move would be done by value, but that doesn't quite work, since: this(ref const(T)); this(T); And: auto t1 = T(42); auto t2 = T(t1); Causes t2 to be constructed with the by value version instead of the copy constructor. Clearly not what we want. So I do this: this(ref const(T)); this(ref T other) { this(*cast(const T*)(&other)); } this(T); And now rvalues go to the by-value version, and lvalues to the copy constructor. What to do with the by-value constructor? pragma(mangle, "<whatever>") this(T* ); // C++ move constructor this(T other) { this(&other); other = T.init; // to avoid problems } And now rvalues go to the C++ move constructor. Since the D definition is in the same module it will probably be inlined as well. The only thing left is to enable explicit moving of lvalues. The thing is that I'm already injecting code into the user's file anyway, so maybe I can use that to enable moves? I can't put the code into a different module (or maybe I can, I might do that later), so to namespace it I put it in a struct called dpp: struct dpp { static struct Move(T) { T* ptr; } static auto move(T)(ref T value) { // by ref so only lvalues apply return Move!T(&value); } } And we emit another constructor with code in it for the user: this(Move!T wrapper) { this(wrapper.ptr); // calls the C++ move constructor } And Bob's your uncle.
Apr 24
next sibling parent reply Uknown <sireeshkodali1 gmail.com> writes:
On Tuesday, 24 April 2018 at 11:19:59 UTC, Atila Neves wrote:
 On Monday, 23 April 2018 at 20:40:47 UTC, Manu wrote:
 On 23 April 2018 at 07:27, Atila Neves via 
 Digitalmars-d-announce <digitalmars-d-announce puremagic.com> 
 wrote:
 On Saturday, 21 April 2018 at 18:11:09 UTC, Manu wrote:
 On 21 April 2018 at 05:41, Atila Neves via 
 Digitalmars-d-announce 
 <digitalmars-d-announce puremagic.com> wrote:
 [...]
Sure. I thought about generating D wrappers for everything, but in TDD fashion I tried slapping a pragma(mangle) on the copy constructor and things just worked. Odd then that dmd doesn't try to correctly mangle constructors and destructors since they're perfectly callable. [...]
This is very cool. Is it possible to fix the mangling issues in DMD for Copy constructors and destructors? It seems like it would be less code for dpp, and better C++ interop for D in general.
Apr 24
parent Atila Neves <atila.neves gmail.com> writes:
On Tuesday, 24 April 2018 at 12:27:30 UTC, Uknown wrote:
 On Tuesday, 24 April 2018 at 11:19:59 UTC, Atila Neves wrote:
 On Monday, 23 April 2018 at 20:40:47 UTC, Manu wrote:
 On 23 April 2018 at 07:27, Atila Neves via 
 Digitalmars-d-announce <digitalmars-d-announce puremagic.com> 
 wrote:
 On Saturday, 21 April 2018 at 18:11:09 UTC, Manu wrote:
 On 21 April 2018 at 05:41, Atila Neves via 
 Digitalmars-d-announce 
 <digitalmars-d-announce puremagic.com> wrote:
 [...]
Sure. I thought about generating D wrappers for everything, but in TDD fashion I tried slapping a pragma(mangle) on the copy constructor and things just worked. Odd then that dmd doesn't try to correctly mangle constructors and destructors since they're perfectly callable. [...]
This is very cool.
Thanks!
 Is it possible to fix the mangling issues in DMD for Copy 
 constructors and destructors?
Yes. And I'm going to have to (see below).
 It seems like it would be less code for dpp,
Eh, it'd be a tiny change.
 and better C++ interop for D in general.
Well, that's the real issue. There are C++ mangling bugs in the dmd frontend, and they need to be fixed because of templates. They don't have a mangling until they're instantiated, and I can't know that ahead of time where the templates are being declared. The easiest way to move forward is to just fix the dmd frontend. Unless I come up with some crazy idea. Which I wouldn't put past me. Atila
Apr 24
prev sibling parent kinke <kinke libero.it> writes:
On Tuesday, 24 April 2018 at 11:19:59 UTC, Atila Neves wrote:
 Odd then that dmd doesn't try to correctly mangle constructors 
 and destructors since they're perfectly callable.
For normal constructors, that only works in the C++-ctor-called-from-D direction, with suboptimal performance. Reason being that the D compiler blits the T.init value over the instance before calling a ctor, and the D ctor not having to define all members. So for a C++ ctor called from D, the pre-construction blit is useless extra work, and calling a D ctor from C++ is unsafe as the D ctor may not set all fields. The latter could be simply implemented by C++ ctor wrappers for all D ctors (for extern(C++) structs and classes), performing the T.init blit and then calling the corresponding D ctor, see https://forum.dlang.org/post/nqxsdehlydizatoprrax forum.dlang.org. For the dtor, IIRC the problem was that it's usually virtual in C++ (at least when planning to allow subtyping) but isn't in D.
Apr 24
prev sibling parent Laeeth Isharc <laeeth laeeth.com> writes:
On Saturday, 21 April 2018 at 12:41:02 UTC, Atila Neves wrote:
 From https://dlang.org/spec/cpp_interface.html:

 "C++ constructors, copy constructors, move constructors and 
 destructors cannot be called directly in D code".

 O RLY?
 Atila
https://www.reddit.com/r/programming/comments/8eat5o/calling_c_constructors_from_d/ https://github.com/atilaneves/dpp
Apr 23