stativ
stativ

Reputation: 1490

Is the use of constexpr valid with non-const members

Background

What I'm trying to achieve: I'm trying to implement something like a Java enum (ie. enum that has some additional functionality). I came up with a solution using two classes, where one class represents a value and the other acts as an enumeration of possible values using static variables to represent each value. I want it to be a real replacement of enum, including the possibility to use the enum values in template instantiation. For this to work, an enum value needs to be a constant expression (constexpr). However, I'm not sure if I use the constexpr correctly.

The Code

Here is the code that I came up with:

class EnumType {
public:
    enum Enum {val_A, val_B, val_C};

    friend class EnumTypeList;

    EnumType()
    : m_ID(val_A), m_foo(0) {}

    constexpr operator Enum() const {return m_ID;};
    constexpr unsigned int getFoo() const {return m_foo;};
protected:
    constexpr EnumType(const Enum v, const int foo)
    : m_ID(v), m_foo(foo) {}
private:
    Enum m_ID;
    int m_foo;
};

class EnumTypeList {
public:
    static constexpr EnumType A = EnumType(EnumType::val_A, 5);
    static constexpr EnumType B = EnumType(EnumType::val_B, 4);
    static constexpr EnumType C = EnumType(EnumType::val_C, 8);
};

The class EnumType holds the information of each value and provides some additional functions (here it is storing the additional value m_foo that can be accessed using getFoo() function). The enumeration itself is represented by the EnumTypeList that contains static constexpr variables, where each variable represents a possible value of an enum. This code allows me to use the variables from EnumTypeList in place of EnumType::Enum. Even in templates like in the following code:

template <EnumType::Enum T>
class Test {
public:
    void test() {
        std::cout << "Enum is not A" << std::endl;
    }
};

template <>
class Test<EnumType::val_A> {
public:
    void test() {
        std::cout << "Enum is A" << std::endl;
    }
};

int main() {
    Test<EnumTypeList::A> a;
    a.test();

    Test<EnumTypeList::B> b;
    b.test();

    // this shouldn't compile
    /*EnumType x = EnumTypeList::C;
    Test<x> c;
    c.test();*/
}

This works as I would expect – I can use the values from EnumTypeList in place of EnumType::Enum in template instantiation as demonstrated above, but I can't do it with EnumType x = EnumTypeList::C;

The Problem

While the code compiles correctly without any warning under gcc and clang, I'm not sure if I use the constexpr correctly. The thing is that while the EnumType constructor and the conversion operator operator Enum() are constexpr, they both access variables m_ID and m_foo that are not constant (because I need assignment operator).

Upvotes: 1

Views: 1476

Answers (1)

Jonathan Wakely
Jonathan Wakely

Reputation: 171333

This is fine, members of literal types are allowed to be modifiable.

In order to use the type in a constant expression you must construct it with constant arguments, but that's OK because you do that;

static constexpr EnumType A = EnumType(EnumType::val_A, 5);

The object constructed is a valid constant expression so can be used to initialize the constexpr variable A. You don't modify the members of the object, so it doesn't matter that they are modifiable.

Clang in particular is very strict about constant expressions, if you were doing something wrong it would give an error.

This object can be used where a constant expression is needed:

constexpr EnumType A5(EnumType::val_A, 5);

e.g.

constexpr int i = A5.getFoo();

This object cannot:

EnumType A6(EnumType::val_A, 6);
constexpr int i = A6.getFoo();  // error 

Upvotes: 3

Related Questions