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

Time:12-18

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 {};
}

CodePudding user response:

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.

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.

CodePudding user response:

[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.

  • Related