4

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.

emik_g
  • 109
  • 5
  • 3
    I think you can find the answer in https://stackoverflow.com/questions/31714790/c14-initializing-constexpr-variables-from-parameter-values – user8510613 Nov 18 '21 at 11:00
  • @user8510613 if you talked about that it is reference problem, I have same error with simple variable, I now fix it in text and same with const cv + simple variable – emik_g Nov 18 '21 at 11:15
  • @emik_g Removing the reference won't solve the problem, because it is still an "id-expression that refers to a variable". You can solve this by directly `return get_init_size(args...)` and remove `header_length` entirely. – Tharsalys Nov 18 '21 at 11:19
  • The thing is that `args` still would be treated as run-time initializers in context constexpr function `make_generic_header`. It CAN be called with run-time values, so exptession that involves them cannot be constexpr in context of `make_generic_header`s body – Swift - Friday Pie Nov 18 '21 at 11:30
  • @Tharsalys I as I said before I isolate this code, in real code I will use this variable as part of parametr in templatesomething like ```something``` – emik_g Nov 18 '21 at 11:34
  • @emik_g I see, but then you'll have to give up on it being constexpr. Basically, you can think of constexpr variables as sort of pivots where the compiler stops and asks, "Do I have all the information I need to initialise this?". If it doesn't, the variable cannot be constexpr. – Tharsalys Nov 18 '21 at 11:36
  • So now only idea is somehow do this with template magic:( – emik_g Nov 18 '21 at 11:46
  • Can't you have `template constexpr size_t get_init_size() { return sizeof...(Args);}` and then `constexpr size_t header_lenght = get_init_size();` – Jarod42 Nov 18 '21 at 13:51
  • Corolary 1: _Don't_ constexpr all the things. – rturrado Nov 18 '21 at 15:14

3 Answers3

5

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.

chris
  • 60,560
  • 13
  • 143
  • 205
  • Can you clarify how the compiler will make it run at compile-time if the variable isn't constexpr? The way I think of it, parameters are only 'known at compile time' at the call site, so if the function itself is invoking another (like in this case, `make_generic_header()`) the next function's parameters aren't `constexpr' as such. – Tharsalys Nov 18 '21 at 11:41
  • Thanks for explaining, what as I said in other answer comment I use the ```header_lenght``` as parameter of template and it must be know on compile time.... – emik_g Nov 18 '21 at 11:44
  • 1
    @Tharsalys, This isn't exactly how it actually works in general, but you can think of a compile-time call to a `constexpr` function as the compiler compiling a separate executable containing that function call and the function's code plus all its dependencies and then running that executable as part of the compilation process. There's a big split between the outermost layer where you first jump into `constexpr` land and all of the nested code inside. Once you're inside, regular runtime code more or less works the same way, but run during compilation. – chris Nov 18 '21 at 11:45
  • @chris So if it's not an over-simplification, will it be right to say you only need to use `constexpr` at the outermost layer, because once inside, it's only a matter of terminology because the context has been decided before? – Tharsalys Nov 18 '21 at 11:48
  • @emik_g, Sorry, I missed that while writing the answer. On top of the function no longer having the same assembly for different parameter values, you straight cannot instantiate a template during constant evaluation. Any template instantiations must be known beforehand. The only way to work around this is to make the value used as a template argument into a template parameter. Then as part of instantiating that template, the inner templates can be instantiated too before constant evaluation. If we end up getting `constexpr` parameters, it's very likely they'll be syntax sugar for a template. – chris Nov 18 '21 at 11:50
  • @chris Thanks for answer, it was helpful, sad that I must write template magic code(( – emik_g Nov 18 '21 at 11:54
  • 1
    @Tharsalys, Yes, that sounds accurate. The functions you call must still themselves be `constexpr` too, but given that, it's regular code down to the leaf functions. When the compiler hits the outer call is when it has to switch gears and spin up its constant evaluation. If it's easier, a more common way to think about it is that the outermost call starts a C++ interpreter that is implemented in the compiler, which then runs the C++ code inside the call in that special environment. When it's all done, the interpreter produces a final result. (Real compilers are based on both of these methods.) – chris Nov 18 '21 at 11:55
3

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.

user8510613
  • 1,242
  • 9
  • 27
  • If I will delete constexpr qualifier code will be executed in runtime? Now this function haven't any runtime code(it just pass arguments that can be runtime, but doesn't use it). And thanks for reference it is really interested! – emik_g Nov 18 '21 at 11:38
  • @emik_g delete the constexpr qualifier on ```header_length``` does not means that the above code will generate some runtime execution, you can check https://godbolt.org/z/zq8h4bq6P, it directly get 3. – user8510613 Nov 19 '21 at 02:05
  • @emik_g https://en.cppreference.com/w/cpp/language/constexpr constexpr variable and constexpr function are different things – user8510613 Nov 19 '21 at 02:49
0

I rewrite the code that will be work and I think will be execute in compile time:

template<typename ... Args>
constexpr auto make_generic_header(const Args ... args) {
    std::integral_constant<size_t, sizeof...(Args)> header_lenght;
    return header_lenght.value;
}

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

I removed function get_init_size and used code part of template parameter(It is guaranteed that it is will be execute on compile time) and after return the number of arguments passed to function(For std::integral_constant for all object value same and know on compile time)

emik_g
  • 109
  • 5