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.