3

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?

clyne
  • 682
  • 4
  • 11
  • `this` is effectively a parameter to member functions. – Nicol Bolas Jun 04 '20 at 00:54
  • @NicolBolas That does seem to be the correct duplicate, but I think you should add an answer there explaining why it isn't allowed even for `consteval` functions. – Brian Bi Jun 04 '20 at 00:56
  • @NicolBolas This is the reference I have, but you may have a different one: https://groups.google.com/a/isocpp.org/d/msg/std-proposals/FvE9w5_onQo/Nbm26D2uBwAJ – Brian Bi Jun 04 '20 at 00:56
  • Instantiating templates during constant evaluation gets messy. – chris Jun 04 '20 at 00:59
  • 1
    @NicolBolas I don't understand how that question answers mine, it doesn't deal with classes or objects at all. It certainly doesn't answer why `this` is unavailable in a consteval constructor. – clyne Jun 04 '20 at 01:00
  • @NicolBolas Is it helpful to know that to `static MyClass mc; mc.config.callback = binder<&mc>()` works? The issue is the assignment within a constructor. – clyne Jun 04 '20 at 01:03
  • @clyne: Classes and objects are irrelevant here; what matters is that you're executing a function. And `this` is a parameter to member functions, including constructors. That's why it's not a constant expression. – Nicol Bolas Jun 04 '20 at 01:11
  • Found the question I was thinking of: https://stackoverflow.com/questions/57226797/will-consteval-allow-using-static-assert-on-function-arguments. And the referenced mental model paper: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0992r0.pdf – chris Jun 04 '20 at 01:18

2 Answers2

6

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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • I understand. However, I would argue that the functionality I'm seeking should be possible in a `constexpr/consteval` context. I've put together an [example](https://godbolt.org/z/JdVnK3) on Compiler Explorer; it appears the compiler can know what it needs to for achieving what I'm looking for, the functionality just isn't there. – clyne Jun 04 '20 at 14:18
  • @clyne: What "should be possible" is irrelevant; the language *does not allow it* at the present time. There are proposals for allowing constexpr parameters (presumably including `this`), but those are in the C++23 (at best) timeframe and thus do not represent C++20. – Nicol Bolas Jun 04 '20 at 14:32
  • I wanted to know why this functionality isn't possible; proposals for making this possible are significant, and it would have been nice to see them included in your answer. Saying this is not possible and leaving it at that reads a lot like "this would never be possible," which appears to not be the case. – clyne Jun 04 '20 at 14:53
  • @clyne: But that's not the question you asked. You asked why the code you wrote doesn't work. And it doesn't work because parameters are not constant expressions in C++. Asking if it is possible to ever modify C++ to make parameters able to be constant expressions is a completely different question. And rather out of scope for Stack Overflow. – Nicol Bolas Jun 04 '20 at 14:58
  • 2
    @NicolBolas: While asking for predictions about decisions that haven’t been made is not appropriate, asking about what technical barriers there might be to extending the language is not, and can be educational. As a relevant example, considering that even a `consteval` function must have *one* return type is very helpful in understanding the lack of constant-expression status for its parameters. – Davis Herring Jun 05 '20 at 14:07
1

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>());
}
clyne
  • 682
  • 4
  • 11