Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler can't execute constexpr expression

I have code something like this:

template<typename ... Args>
constexpr size_t get_init_size(Args ... args) {
    return sizeof...(Args);
}

template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
    constexpr size_t header_lenght = get_init_size(args...);
    return header_lenght;
}

constexpr auto create_ipv4_header() {
    constexpr auto x = make_generic_header(0b01, 0b10, 0b01);
    return x;
}

I know it is dummy code, but I isolate it to find error.

Compiler give me error(GCC):

In instantiation of 'constexpr auto make_generic_header(Args&& ...) [with Args = {int, int, int}]':
/tmp/tmp.CaO5YHcqd8/network.h:39:43:   required from here
/tmp/tmp.CaO5YHcqd8/network.h:31:22: error: 'args#0' is not a constant expression
   31 |     constexpr size_t header_lenght = get_init_size(args...);
      |                      ^~~~~~~~~~~~~

I tried add qualifier const to function parameters but it same doesn't work. In theory all this function can calculate in compile time. But where is problem I can't find with my knowledge.

like image 462
emik_g Avatar asked Jan 24 '23 05:01

emik_g


2 Answers

constexpr means different things for variables vs. functions.

For variables, it means that the variable must be compile-time. Therefore, it needs to be initialized with a constant expression.

For functions, constexpr means the function can be run at compile-time in addition to runtime using the same code inside. Therefore, everything you do inside must be applicable for a runtime call as well.

With that in mind, let's study make_generic_header:

template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
    constexpr size_t header_lenght = get_init_size(args...);
    return header_lenght;
}

Here, header_lenght is a constexpr variable, so must be compile-time. Therefore, get_init_size(args...) must be done at compile-time as well. However, parameters are not constant expressions for various reasons, so this won't work. If this did work, it would mean that make_generic_header, a constexpr function, is unusable at runtime¹, which doesn't fit its requirement of being usable at both compile-time and runtime.

The fix is rather simple: Use code that works in both cases:

template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
    size_t header_lenght = get_init_size(args...);
    return header_lenght;
}

Did you spot it? The only change is to remove constexpr from header_lenght. If this function is run at compile-time, it will still work out and the overall function call expression will be a constant expression.


¹"But it won't work in consteval either!" - True, the reasoning given in the answer is sufficient for constexpr, but I've left out the more fundamental reason since it's not relevant here.

like image 67
chris Avatar answered Jan 26 '23 18:01

chris


It's not about the reference problem. And constexpr variable and constexpr function are different things. Quoted from the answer https://stackoverflow.com/a/31720324:

image

The reference does not have a preceding initialization from the point of view of i, though: It's a parameter. It's initialized once ByReference is called.

This is fine, since f does have preceding initialization. The initializer of f is a constant expression as well, since the implicitly declared default constructor is constexpr in this case (§12.1/5). Hence i is initialized by a constant expression and the invocation is a constant expression itself.

And about "preceding initialization", quoted from this:

It does mean "be initialized", but it's more importantly about the visibility of a preceding initialization within the context of the expression being evaluated. In your example, in the context of evaluating func(0) the compiler has the context to see the initialization of rf with 0. However, in the context of evaluating just the expression rf within func, it doesn't see an initialization of rf. The analysis is local, as in it doesn't analyze every call-site. This leads to expression rf itself within func not being a constant expression while func(0) is a constant expression.

Corresponding to your case, the line:

constexpr size_t header_length = get_init_size(args...);

From the point of view of header_length, get_init_size(args...) is not a core constant expression, since its invoke argument is from the function's argument, and args is not a core constant expression either.

Simple modification can make your code work, you can remove the constexpr qualifier from header_length, or directly return get_init_size(args...); in make_generic_header's function body.

I hope this and this might also help you.

like image 34
user8510613 Avatar answered Jan 26 '23 19:01

user8510613