Thread overview
Who says we can't call C++ constructors?
Apr 21, 2018
Atila Neves
Apr 21, 2018
12345swordy
Apr 21, 2018
Manu
Apr 23, 2018
Atila Neves
Apr 23, 2018
Manu
Apr 24, 2018
Atila Neves
Apr 24, 2018
Uknown
Apr 24, 2018
Atila Neves
Apr 24, 2018
kinke
Apr 23, 2018
Laeeth Isharc
April 21, 2018
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

April 21, 2018
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!
April 21, 2018
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 ;)
April 23, 2018
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
April 23, 2018
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.
April 23, 2018
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.
April 24, 2018
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.
April 24, 2018
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.
April 24, 2018
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
April 24, 2018
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.