3

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 {};
}
1
  • 1
    This doesn't address the question, but return 2^(32 - shortForm) - 1; looks like a mistake. ^ is bitwise xor; it is not exponentiation. Commented Dec 17, 2022 at 14:35

2 Answers 2

3

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)
Sign up to request clarification or add additional context in comments.

1 Comment

"you can't call a member function of a class that is in this state.": Of course you can do that in general. However, whether the call can appear as part of a core constant expression is another question, which I think the standard currently doesn't really specify well, see my answer.
1

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

4 Comments

No, it's really important that the definition of member functions always appear at the end of a class. That allows them to include the usage of members that aren't yet defined in the class at the location where the function definition sits. That is not a matter of how compilers "implement" it; that's how the standard spells out how it must be implemented. And the standard is clear about this.
@NicolBolas The standard currently doesn't say that. It only says that name lookup is performed as if from the closing }. And as seen in this question, it doesn't really result in intuitive behavior. Maybe the evaluation of the constexpr initializer should then also be delayed to the end of the class definition.
"The standard currently doesn't say that." Um, yes it does. I can't point to the actual part of the standard that does it, but this is why the CRTP even works. It's also why CRTP member functions can access derived class members, but CRTP member typedefs and variables cannot. "it doesn't really result in intuitive behavior" In that instance. In other instances, it makes perfect sense.
@NicolBolas The term is only used twice: Once to list the complete-class contexts in [class.mem] and once to specify how that affects the visible names during lookup in [class.member.lookup]. Nothing about the placement of the definition for other purposes. CRTP is different because instantiation of the members is delayed and the point of instantiation is (mostly) defined, although there is also CWG 287, which it seems everyone just agrees to implement as suggested there, in non-conformance to the standard.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.