Matexer
Matexer

Reputation: 38

ERR: Constexpr variable must be initialized by a constant expression, but it is

I am using clangd as LSP, with C++20 and I have stuck with such a problem. I have no idea why I cannot initialize a constexpr array with a constexpr function. Inserting a direct values works but I would like to understand why my "automatic" solution doesn't work.

Error returned by clangd LSP is probably because of a second issue, but I don't know why defined function is not seen as defined.

IpRange.h

class IpRange {
    inline static constexpr uint32_t getMaskValue(const ushort shortForm) {
        return 2^(32 - shortForm) - 1;
    }

    template <typename T>
    inline static constexpr T getMaskValues() {
        auto masks = T();
        for (ushort i = 0; i < 33 ; i++) {
            masks[i] = getMaskValue(i);
        }
        return masks;
    }

    static constexpr std::array<uint32_t, 33> maskValues { getMaskValues<std::array<uint32_t, 33>>() };

    //Returns clangd error: Constexpr variable 'maskValues' must be initialized by a constant expression
    //Returns compilation error: constexpr T IpRange::getMaskValues() [with T = std::array<unsigned int, 33>]’ used before its definition

    static constexpr std::array<uint32_t, 33> maskValues { 1, 2, 3 };
    //Works fine...
}

Solution. Moving Masks-stuff to another class.

#define MASK_VAL_NUM 33

class MaskValues {
    typedef std::array<uint32_t, MASK_VAL_NUM> MaskValA;

    inline static constexpr uint32_t getMaskValue(const ushort shortForm) {
        return 2^(32 - shortForm) - 1;
    }

    inline static constexpr MaskValA getMaskValues() {
        auto masks = MaskValA();
        for (ushort i = 0; i < MASK_VAL_NUM ; i++) {
            masks[i] = getMaskValue(i);
        }
        return masks;
    }

    const std::array<uint32_t, MASK_VAL_NUM> maskValues;

public:
    constexpr MaskValues(): maskValues{getMaskValues()} {
    }

    const uint32_t& operator[](std::size_t idx) const {
        return this->maskValues[idx];
    }
};


class IpRange {
    static constexpr MaskValues maskValues {};
}

Upvotes: 2

Views: 1627

Answers (2)

The Moisrex
The Moisrex

Reputation: 2077

G++ gives a better error message for this one than Clang:

main.cpp:19:99: error: ‘static constexpr T IpRange::getMaskValues() [with T = std::array<unsigned int, 33>]’ used before its definition
   19 | es<std::array<uint32_t, 33>>();
      | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~

The problem is that you're calling the getMaskValues while the definition of the class is not yet complete and you can't call a member function of a class that is in this state (in a non-static members).

Even if you remove the templates, you will still get this error message from GCC:

main.cpp:18:73: error: ‘static constexpr std::array<unsigned int, 33> IpRange::getMaskValues()’ called in a constant expression before its definition is complete
   18 |     static constexpr std::array<uint32_t, 33> maskValues = getMaskValues();
      |                                                            ~~~~~~~~~~~~~^~

Essentially you can't call IpRange::getMaskValues while IpRange is still not complete.

To fix this:

  • You can move getMaskValues (and whatever it needs) into a separate class (moving into sub-classes won't work) or define them globally
  • You can make the IpRange class a templated class.
  • Make maskValues a non-static member (as @user17732522 pointed out)

Upvotes: 3

user17732522
user17732522

Reputation: 76688

[expr.const]/5.3 does not allow invocation of an "undefined" function in a constant expression evaluation.

However, it is not clearly defined what "undefined" means in that context. See also e.g. CWG issue 2166.

The problem here is that the bodies of functions in a class definition are a so-called complete-class context. This means that name lookup from inside the function bodies is performed as if the closing } of the class definition had already been reached. This way members that haven't been declared yet can be found. In practice that means that the compiler is going to move the function definition to after the closing } of the class.

So then getMaskValues is defined only after the closing } of the class, but it is called in a context that requires constant expression evaluation from within the class definition (the initializer of maskValues), which results in an error.

However, I would argue that both getMaskValues functions are clearly defined before the point where they are used. That the function bodies are supposed to be in a complete-class context, meaning that they should perform name lookup as if they were placed after the closing } of the class definition, doesn't affect where they are defined and the standard doesn't say anything about a point of definition depending on the complete-class context.

Of course this poses a bit of a problem where the compiler needs to evaluate the body of the member function for the constant expression evaluation and at the same time needs to delay name lookup for that evaluation to the end of the class definition.

So this seems to me like an issue in the standard. It should specify that definitions which are a complete-class context are considered to be "defined" only after the closing } of the class definition. That's how compilers seem to implement it at the moment and which would expectedly result in the error you see. Alternatively I could imagine clarifying that the evaluation of a constexpr initializer should be delayed until the end of the class definition as well. However, this would still leave other cases unresolved, e.g. when the function is called as part of a type of a member.

Upvotes: 1

Related Questions