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.

9
  • 3
    I think you can find the answer in stackoverflow.com/questions/31714790/… Commented Nov 18, 2021 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 Commented Nov 18, 2021 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. Commented Nov 18, 2021 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_headers body Commented Nov 18, 2021 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<header_length> Commented Nov 18, 2021 at 11:34

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

7 Comments

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.
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....
@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 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, 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.)
|
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.

3 Comments

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 delete the constexpr qualifier on header_length does not means that the above code will generate some runtime execution, you can check godbolt.org/z/zq8h4bq6P, it directly get 3.
@emik_g en.cppreference.com/w/cpp/language/constexpr constexpr variable and constexpr function are different things
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)

Comments

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.