Why isn't "this" known at compile time in a consteval constructor?

Viewed 924

I'm writing a class to wrap around part of a C-based library that works with devices, where each device is configured with a callback function poiner for handling data. An instance of MyClass would be made for each device. See below:

struct DeviceConfig {
    void (*callback)(char *data);
};

class MyClass {
private:
    DeviceConfig config;

public:
    void myCallback(char *data);

    MyClass() {
        // Would like to set config.callback so that a call to it will result in a call of this->myCallback(data).
    }
};

Since a capturing lambda can't be converted to a function pointer, I've tried the following as a workaround:

template<MyClass *MC>
auto binder() {
    return [](char *data) { MC->myCallback(data); };
}

MyClass::MyClass() {
    config.callback = binder<this>();
}

However, the compiler (GCC latest) doesn't like binder being used in the constructor, as this isn't necessarily known at compile-time, though I know that instances of MyClass will only be declared at compile-time.

C++20 introduced consteval functions (and constructors) which "must produce a compile-time constant.". However, adding consteval to the constructor and/or binder doesn't affect the compiler's output. constexpr doesn't change things either.

If an object can be initialized at compile-time, why can't the object's this be known at compile-time too? Can the above be achieved through some other manner?

2 Answers

Constructors are functions, just like any other. They have very few special privileges compared to other functions, and the constant expression behavior of their parameters is not one of them.

this is essentially a parameter to all non-static member functions. Parameters are never constant expressions. Therefore, this can not be used in a context that requires a constant expression. It does not matter how you create a class instance. It does not matter how you call it. Parameters to constexpr/consteval functions are never constant expressions, and that includes this.

The accepted answer does explain why this can't be used within the constructor; however, I also asked if there was a workaround to achieve what I wanted. I've found a workaround, and shared that work in another StackOverflow post here.

To save a click, here's the last iteration of my code:

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

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

class MyClass {
private:
    DeviceConfig config;

public:
    consteval MyClass(const DeviceConfig& 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>());
}
Related