Search code examples
c++c++11cyclic-reference

Cyclical constexpr


I'd like to construct some state machines using constexpr in C++, which requires the states and their transitions to be constexpr as well. If the state machine has a cyclical portion (eg. state 1 has a transition to state 2, and state 2 has a transition to state 1), then cyclical constexpr is required. However, code such as the following doesn't compile (MSVC 2017) as constexpr must be initialized at declaration:

class a
{
public:
    constexpr a(a const & v) : v(v) {}
    a const & v;
};

constexpr extern a x;
constexpr a y = a(x);
constexpr a x = a(y);

Is there a workaround, or a plan to address this in a future revision of the C++ standard?

Edit: Based on erenon's comment, I tried this:

class a
{
public:
    constexpr a(a const & v) : v(v) {}
    a const & v;
};

constexpr a x_getter();

constexpr a y_getter() {
    return a(x_getter());
}

constexpr a x_getter() {
    return a(y_getter());
}

constexpr a test = x_getter();

but it fails with an interesting warning message: Warning C4591 'constexpr' call-depth limit of 512 exceeded and expression did not evaluate to a constant errors at the last line.


Solution

  • You can hide x and y inside a class (or struct). Even if you did not want to have recursive references, this would be beneficial for reasons of information encapsulation:

    #include <iostream>
    
    class a
    {
    public:
        // use `a const *` to avoid overriding default copy-constructor
        constexpr a(a const *v_) : v(*v_) {}
        a const & v;
    };
    
    struct state_machine
    {
        a const x,y;
        constexpr state_machine() : x(&y), y(&x) {}
    private:
        // can't copy or move a state_machine (this implicitly deletes operator=):
        state_machine(state_machine&&) = delete;
    };
    
    constexpr state_machine sm;
    
    int main(int argc, char **argv) {
        std::cout << "&x: " << &sm.x << ", &x.v: " << &sm.x.v << '\n';
        std::cout << "&y: " << &sm.y << ", &y.v: " << &sm.y.v << '\n';
        // state_machine sm2{sm}; // forbidden
    }
    

    Above code was tested with g++ versions 5.4.1 and 6.2.0 using -std=c++11 and -std=c++14. I don't have MSVC to check that.