Can I initialize an object's C style function

2020-06-29 09:56发布

I'm writing a class to wrap around a library that requires callback function pointers. See below:

struct LibraryConfig {
    // Omitting other members...
    void (*callback)(const char *);
};

class MyClass {
private:
    LibraryConfig m_config;

public:
    MyClass(const LibraryConfig &config) {
        // Initialize m_config using config, would like to set callback so that it calls
        // this->myCallback().
    }

    void myCallback(const char *);
};

Only static instances of MyClass will be declared, so construction can be kept within compile-time. I've tried lambdas and template functions that take MyClass pointers, but I either can't accomplish this within the constructor, or can't achieve this at compile-time (getting the address of an instance through this or &myClass at compile-time doesn't seem possible).

constexpr parameters may be allowed in the future, making this trivial to implement, but is there a way to accomplish this right now with C++20?

标签: c++ c++20
1条回答
我欲成王,谁敢阻挡
2楼-- · 2020-06-29 10:27

Yes, this is apparently possible. See the below snippet:

struct LibraryConfig {
    void (*callback)(const char *);
};

class MyClass {
private:
    LibraryConfig config;

public:
    consteval MyClass(const LibraryConfig& cfg) :
        config(cfg) {}

    void myCallback(const char *data);
};

int main()
{
    constinit static MyClass mc = {{
        [](const char *data) { mc.myCallback(data); }
    }};
}

See a working example on Compiler Explorer here. Since mc is static, the lambda is allowed to access it without capture. There may be room for improvement on this solution, e.g. having the lambda be produced by a function.

Edit:

I've come up with a generalized function to create the lambda:

template<auto& inst, auto func>
consteval auto make_member_callback()
{
    return []<typename... Args>(Args... args) { (inst.*func)(args...); };
}

This allows the following to be possible (compiler explorer):

constinit static MyClass mc {{
    make_member_callback<mc, &MyClass::myCallback>()
}};

Edit 2:

Here is my final try at this, where the initialization is done within the class:

struct LibraryConfig {
    void (*callback)(const char *);
};

template<auto& v, auto f>
constexpr auto member_callback = [](auto... args) { (v.*f)(args...); };

class MyClass {
private:
    LibraryConfig config;

public:
    consteval MyClass(const LibraryConfig& cfg = {}) :
        config(cfg) {}

    template<MyClass& MC>
    constexpr static MyClass& init() {
        MC.config.callback = member_callback<MC, &MyClass::myCallback>;
        return MC;
    }

    void myCallback(const char *data);
};

int main()
{
    constinit static MyClass mc = (mc = MyClass(), MyClass::init<mc>());
}
查看更多
登录 后发表回答