Force C++ to move out of an object when invoking external C function that takes object by value

I have a C function in a DLL that takes an object by value. I am trying to generate C++11 bindings for the DLL. In C, everything works normally:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct App {
    const void* ptr;
} App;

extern DLLIMPORT App AzApp_create();
extern DLLIMPORT void AzApp_run();

int main() {
    App a = AzApp_create();
    AzApp_run(a);
    return 0;
}


However when I'm trying to wrap this code into C++ classes, C++ doesn't want to move out of the "app" object when invoking the run() method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <utility>

/* backport std::exchange for C++11 */
template<class T, class U = T>
T exchange(T& obj, U&& new_value)
{
    T old_value = std::move(obj);
    obj = std::forward<U>(new_value);
    return old_value;
}

struct App {
    void* ptr;

    /* no default constructor, use App::create() */
    App() = delete;
    /* implement move assign */
    App (App&& o) noexcept : ptr(exchange(o.ptr, nullptr)) { }
    
    public:
        static App create();
        int run();
};

extern "C" {
    App AzApp_create();
    int AzApp_run(App);
}

App App::create() {
    return AzApp_create();
}

void App::run() {
    return AzApp_run(std::move(this)); /* error here */
}

int main() {
    auto app = App::create();
    app.run();
    return 0;
}


The error is:


main.cpp:44:31: error: could not convert ‘std::move(((App*)this))’ from ‘std::remove_reference::type {aka App*}’ to ‘App’
     return AzApp_run(std::move(this));
                      ~~~~~~~~~^~~~~~


How do I force C++ to move from the "app" object? Ideally I do NOT want to copy the app object or invoke the copy constructor.

The context of this code is that it's generated code for my GUI toolkit https://azul.rs/ (which is written in Rust with a C binding layer), so both in C and in Rust I am able to "move" out of the object, but not in C++?

The API I'm trying to bind is here:

https://github.com/fschutt/azul/blob/71a18517c595c3fdb4a06040b24fc1fc13e485ae/api/cpp/azul.hpp#L8715

Thanks in advance for any help.
Last edited on
How do I force C++ to move from the "app" object?
So what exactly does 'move' mean in this context? std::move(...) is just a kind of cast. See:

http://www.cplusplus.com/reference/utility/move/?kw=move
1
2
3
4
5
6
7
8
9
10
11
12
 // your c declaration 
void AzApp_run(); //any number of parameters, no return value

// your c++ declaration
int run(); //returns an int
//your c++ implementation
void App::run() //no return value

// your extern c declaration
int AzApp_run(App); // returns an int, expects an App object
// your api declaration
void App_run(const App* app, AzWindowCreateOptions  window); //no return value, receives an App pointer and some other thing 


> I do NOT want to copy the app object or invoke the copy constructor.
pass a pointer, like the api ask

> std::move(this)
looks like a good way to amputate your foot
@coder777: I want to express that I'm moving the ownership of the "app" object into the function call. There is no more code after calling run(), because the run() method "consumes" the app object immediately after it is finished.

In C it's possible to do this:

 
AzApp_run(AzApp_create());


In this case the constructed AzApp object returned by AzApp_create() is directly passed (by-value) into the first argument slot of AzApp_run(), so that the object is consumed by the run() function. The "ownership" is immediately moved from the parent main() function into the run() function without any intermediate copies. The compiler can see this and optimize accordingly. This is the default in Rust (except for trivially-copyable types such as integers). In C++ you can annotate your class to use move constructors instead of copy constructors with:

1
2
/* tell C++ to use move constructor instead of copy constructor */
App (App&& a) noexcept = default;


This implicilty deletes the copy constructor, so then you HAVE to use std::move if you do anything with your object (which is good, std::move is usually better for performance). My problem in the code was that the "this" pointer is a "*App", but in order to invoke the C function I need a "App" object (i.e. not just the pointer).
I've figured out what the problem was: I just need to dereference the "this" pointer when using std::move, because std::move expects a rvalue instead of a prvalue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <utility>

struct App {
    void* ptr;

    App() = delete;
    /* tell C++ to use move constructor instead of copy constructor */
    App (App&& a) noexcept = default;
    
    public:
        static App create();
        int run();
};

extern "C" {
    App AzApp_create();
    int AzApp_run(App);
}

App App::create() {
    return AzApp_create();
}

int App::run() {
    return AzApp_run(std::move(*this)); /* std::move(*this) instead of std::move(this) */
}

int main() {
    auto app = App::create();
    return app.run();
}


@ne555: my example wasn't the actual API, I wanted to keep the example code as short as possible. The final code would then look something like this:

1
2
3
4
5
6
7
8
9
10
11
// ...

int App::run(const WindowCreateOptions&& window) {
    return AzApp_run(this, std::move(window)); /* std::move(*this) instead of std::move(this) */
}

int main() {
    auto window = WindowCreateOptions::default();
    auto app = App::create();
    return app.run(std::move(window));
}


So that should do it. Thanks!
Last edited on
The implicit object parameter of a member function that moves from *this should be a non-const rvalue reference. A feature called ref-qualification offers this functionality.
1
2
3
struct App { int run() && { AzApp_run(std::move(*this)); } };
// ...
std::move(app).run(WindowCreateOptions::default_());

https://en.cppreference.com/w/cpp/language/member_functions#ref-qualified_member_functions

App::run should be justified given that AzApp_run does the same thing and behaves more conventionally. The law of least surprise would suggest avoiding App::run.

I just need to dereference the "this" pointer when using std::move, because std::move expects a rvalue instead of a prvalue:

All prvalues are rvalues. The problem is that the expression this has the wrong type.
Last edited on
Topic archived. No new replies allowed.