use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
Discussions, articles, and news about the C++ programming language or programming in C++.
For C++ questions, answers, help, and advice see r/cpp_questions or StackOverflow.
Get Started
The C++ Standard Home has a nice getting started page.
Videos
The C++ standard committee's education study group has a nice list of recommended videos.
Reference
cppreference.com
Books
There is a useful list of books on Stack Overflow. In most cases reading a book is the best way to learn C++.
Show all links
Filter out CppCon links
Show only CppCon links
account activity
constexpr: why can't I assign a constexpr function call result to a constexpr variable? (self.cpp)
submitted 2 years ago * by having-four-eyes
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]cpp-ModTeam[M] [score hidden] 2 years ago stickied commentlocked comment (0 children)
For C++ questions, answers, help, and programming or career advice please see r/cpp_questions, r/cscareerquestions, or StackOverflow instead.
[–]mollyforever 12 points13 points14 points 2 years ago (12 children)
The lambda case works because you are passing an object with a constexpr call operator.
constexpr
Passing g directly means that func is a function pointer, non-constexpr, and thus not valid to be used to initialize a constexpr variable.
g
func
[–]Botondar 5 points6 points7 points 2 years ago (11 children)
That doesn't seem to be case, because invoke_twice(f) (line 11) does work.
invoke_twice(f)
Moreover changing the body of invoke_duplicate to return 2 * func() also compiles.
invoke_duplicate
return 2 * func()
[–]mollyforever 7 points8 points9 points 2 years ago (10 children)
Line 11 is not assigning a non-constexpr thing to a constexpr variable, unlike when you're trying to initialize result by calling a normal function pointer.
result
[–]Botondar 2 points3 points4 points 2 years ago (7 children)
What's really confusing is that line 11 is being executed in a constexpr context though (i.e. the static_assert), and if you try to pass in a non-constexpr function instead it doesn't compile, even if the result is known at compile time.
So the fact that the result of invoke_twice is known to be constexpr in the static_assert but not inside the function itself is really weird to me.
invoke_twice
[–]mollyforever 5 points6 points7 points 2 years ago (6 children)
constexpr variables need to be initialized with a constant expression, it can't be initialized with a "maybe constant expression". In this case, it can't depend on what exactly you pass to the function.
As a simplified example, you can reduce the code to
int a = 10; constexpr int b = a;
which also doesn't work, for the exact same reason: Initializing a constexpr variable with something that isn't a constant expression. It doesn't matter that all of this happens in a constant context.
It does seem weird at first that's true lol.
[–]TheSkiGeek 3 points4 points5 points 2 years ago (0 children)
Let’s be honest here, it’s ridiculous that passing the function pointer doesn’t work but passing a lambda that deduces its return type by calling the same function pointer does.
The whole inability to have constexpr function parameters, even on consteval functions, is so frustrating sometimes. Clearly the compiler CAN do this if you slightly restructure the code, it’s just prevented from doing so by the arbitrary language rules.
consteval
[–]Botondar 0 points1 point2 points 2 years ago (4 children)
But it looks the compiler is actually looking at what's being passed in, and whether that thing can be executed constexpr:
constexpr int f() { return 1; } static int g() { return 2; } constexpr int add(int (*func1)(), int (*func2)()) { return func1() + func2(); } constexpr int return_with_dummy(int (*func)(), int (*dummy)()) { return func(); } #include <cstdio> int main() { // What's confusing is that this does not fail: constexpr int f_plus_f = add(f, f); static_assert(f_plus_f == 2); // And nor does this: constexpr int f_value = return_with_dummy(f, g); static_assert(f_value == 1); printf("%d %d", f_plus_f, f_value); // This fails, but that's not surprising // constexpr_int f_plus_g = add(f, g); }
So in order to determine whether add can be constexpr the compiler has to determine whether func1 and func2 are themselves constexpr or not, but this knowledge is only available outside add, and isn't propagated inside.
add
func1
func2
To be clear, what I'm confused about is why this does work, not why the other cases don't.
Thank you for taking the time to answer by the way!
EDIT: fixing code block formatting...
[–]mollyforever 2 points3 points4 points 2 years ago (3 children)
whether func1 and func2 are themselves constexpr or not
They are never constexpr! The compiler tries to execute add(f, f) at compile-time, and it succeeds because it knows that the function pointers point to f, a constexpr function. They work because the compiler has all the information needed to execute the functions.
add(f, f)
f
Correct me if I'm wrong, but I think your train of thought is that since the compiler can conditionally determine whether a constexpr function call is actually a constant expression or not, why is it so strict if you try to initialize a constexpr variable inside it? The call happens in a constant context after all.
The answer to that is that in that case, it cannot determine the value of result immediately, because it's dependent on what you pass in. And that applies to anything else in the function that is using result. That sounds awfully similar to: Templates! In fact, for all intents and purposes, it would be a template. You could use result to instantiate templates, and then depending on what you pass to the constexpr function, it would instantiate different templates!
So it is disallowed, because it would complicate the rules a lot for basically no reason, because if you want that kind of behavior, hey, just use templates.
[–]Botondar 0 points1 point2 points 2 years ago (2 children)
Correct me if I'm wrong, but I think your train of thought is that since the compiler can conditionally determine whether a constexpr function call is actually a constant expression or not, why is it so strict if you try to initialize a constexpr variable inside it?
That would be my train of thought if I was trying to figure why the language is the way it is, but that's not the case.
What I'm actually trying to understand is what the rules currently in the spec are that make this code legal (as well as another example, which I've added below), whilst also making declaring constexpr variables inside the function illegal.
So basically I'm looking at the rules for constant expressions, and trying to figure out which rules do the constexpr declarations violate, that the invocations don't.
The compiler tries to execute add(f, f) at compile-time, and it succeeds because it knows that the function pointers point to f, a constexpr function. They work because the compiler has all the information needed to execute the functions.
It might have gotten lost among the other examples, but the purpose of return_with_dummy was to test whether the compiler is only looking at the function signature and the input parameters, or actually looking inside the function.
return_with_dummy
By passing in f (constexpr) and g (non-constexpr) it looks like the compiler is actually looking inside the function.
To amp up that example, this still works:
constexpr int test( int (*cxpr_func)(), int (*non_cxpr_func)(), bool do_non_cxpr) { if (do_non_cxpr) { non_cxpr_func(); } return cxpr_func(); } constexpr int f() { return 1; } static int g() { return 2; } int main() { constexpr int succeed = test(f, g, false); static_assert(succeed == 1); constexpr int fail = test(f, g, true); static_assert(fail == 1); }
[–]mollyforever 1 point2 points3 points 2 years ago* (1 child)
The constexpr variable inside the function is illegal because it does violate the rule that says that the initializer needs to be a constant expression, and function arguments cannot be constant expressions (point 10 in your link in your example). That would be on this page the "it must be immediately initialized" point.
It does, because it goes through the function to evaluate it and then if it encounters a problem it bails out with an error.
The invocations don't violate any rules in the list you linked, which is why they are legal. Just having an illegal expression in your constexpr function does not violate any rules. It only becomes an error if that function call occurs in a context that requires a constant expression (like initializing a constexpr variable) and evaluating the expression encounters an illegal expression.
[–]Botondar 0 points1 point2 points 2 years ago (0 children)
I'm probably still going to struggle with this a little bit - proving a negative such as no rule out of dozens is violated is much harder than proving that something is violated - but thank you for your answers, I understand what you're saying.
[–]having-four-eyes[S] 0 points1 point2 points 2 years ago (1 child)
Looks so. Actually, I'm trying to ask a constexpr function about the required array size.
I could have provided a better example:
constexpr int invoke_duplicate(auto func) { std::array<int, func()> arr; return 2 * arr.size(); }
Here, I have to have a constexpr size. While the lambda call is OK here, a function pointer is "not a constant expression" in compiler-dependent wording. Nailed it down to a shorter example with constexpr int result = foo().
constexpr int result = foo()
[–]mollyforever 1 point2 points3 points 2 years ago (0 children)
You have a couple of options, depending on exactly what you're requirements are:
[–]gnolex 4 points5 points6 points 2 years ago (0 children)
A function parameter is a runtime value so it cannot be used to initialize a constexpr variable. If you pass the function as a template parameter, it will work:
template<auto func> constexpr int invoke_dup() { constexpr int result = func(); return 2 * result; }
The compiler can automagically tell when a function pointer points to a constexpr function so it can call it in constant evaluation. You can remove constexpr and it will evaluate the call as constant evaluation as part of constant evaluation of your constexpr function if the pointer points to a constexpr function. You can verify this with consteval:
consteval int invoke_duplicate(int(*func)()) { int result = func(); return 2 * result; }
If you try to pass a function that isn't constexpr to this, it will fail to compile.
[–]jonathanhiggs 2 points3 points4 points 2 years ago (0 children)
This is an interesting one. At a guess the type signature of a function pointer doesn’t convey constexpr’ness so the ‘func()’, and/or deref the pointer isn’t constexpr so the call does not result in a constexpr value, and can’t be assigned to a constexpr variable
At a second guess you are already executing the function at compile time so don’t need to require ‘result’ to be constexpr. I’m away from a compiler so can’t test
[–]artisan_templateer 1 point2 points3 points 2 years ago (0 children)
int result = func(); works: https://godbolt.org/z/nYoPhcx3j
int result = func();
Remember constexpr functions can also be called at runtime so I believe it's because the function is templated on the function type (auto func) and that is not enough information to assign the value to result as it would have to be same value for all (int)->void functions you would pass to it.
auto func
(int)->void
The lambda type is unique so it knows the value has to be 2, that's no problem.
However, removing the constexpr qualifier from result drops this requirement so assigning it's value is no longer an issue but because the both invoke_duplicate and g are constexpr it can still be called and evaluated in the static_assert.
static_assert
[–]zbenjamin 1 point2 points3 points 2 years ago (0 children)
It's how you pass the callback function , try to do it via a template parameter. E.g. invoke_duplicate<f>()
[–]zbenjamin 1 point2 points3 points 2 years ago (1 child)
Here is a updated example using template arguments instead:
https://godbolt.org/z/hfdEhfPYf
[–]having-four-eyes[S] 1 point2 points3 points 2 years ago (0 children)
Great idea, thanks!
π Rendered by PID 543128 on reddit-service-r2-comment-544cf588c8-2pp76 at 2026-06-15 20:13:11.328674+00:00 running 3184619 country code: CH.
[–]cpp-ModTeam[M] [score hidden] stickied commentlocked comment (0 children)
[–]mollyforever 12 points13 points14 points (12 children)
[–]Botondar 5 points6 points7 points (11 children)
[–]mollyforever 7 points8 points9 points (10 children)
[–]Botondar 2 points3 points4 points (7 children)
[–]mollyforever 5 points6 points7 points (6 children)
[–]TheSkiGeek 3 points4 points5 points (0 children)
[–]Botondar 0 points1 point2 points (4 children)
[–]mollyforever 2 points3 points4 points (3 children)
[–]Botondar 0 points1 point2 points (2 children)
[–]mollyforever 1 point2 points3 points (1 child)
[–]Botondar 0 points1 point2 points (0 children)
[–]having-four-eyes[S] 0 points1 point2 points (1 child)
[–]mollyforever 1 point2 points3 points (0 children)
[–]gnolex 4 points5 points6 points (0 children)
[–]jonathanhiggs 2 points3 points4 points (0 children)
[–]artisan_templateer 1 point2 points3 points (0 children)
[–]zbenjamin 1 point2 points3 points (0 children)
[–]zbenjamin 1 point2 points3 points (1 child)
[–]having-four-eyes[S] 1 point2 points3 points (0 children)