r/cpp_questions • • 1d ago

OPEN Guidelines for constexpr

I’ve working with C++23 and I’m making some constructors/methods constexpr and seeing improvements in performance. So far so good. The problem is I don’t want to apply it ‘everywhere’. Is there a good source of advice , or anyone got any good tips?

13 Upvotes

35 comments sorted by

12

u/the_poope 1d ago

Yes, first you need to understand why it may give performance improvements. Are you 100% sure you actually understand what constexpr does? If you know that, then you should know where you can use it for performance gains.

1

u/Slow_Negotiation_935 1d ago

Well I understand what it does (mostly) and I can apply the thing. I’m just not sure than blanket defining my numerical ops (I.e +) as constexpr is a good way to proceed.

13

u/the_poope 1d ago

constexpr means that the compiler can evaluate the expression at compile time before the program is even run. It is basically modifying the source code before it actually compiles it to simplify it using the information already present in the source code. I.e. it is doing simple operations you could have done by hand, by typing it out.

In order to do this, the expression/function it evaluates can only rely on information and values that is directly written in the source code.

So the big question is: why is your program performance depending so much on stuff that could be precomputed and stuffed right into the source code? The idea of a computer program is to evaluate information that is available only when you run it, e.g. from user input.

Of course evaluating static expressions by hand to insert the precomputed data directly into the source code may reduce code readability and maintainability - and that is why we have constexpr. Instead of tabulating precomputed data (=magic numbers) you can let the compiler do the tabulation from simpler data and logic.

So there's your advice: Use constexpr where you need to precompute data that's supposed to be hardcoded in the program and where having expressions is more maintainable than just data tabulation and where the computation is not desirable to perform at runtime.

5

u/bert8128 1d ago

What’s wrong with everywhere? Is there a downside?

6

u/oriolid 1d ago

Clutter, and the fact that it often doesn't do anything useful. Compilers can evaluate functions at compile time as part of normal optimization, and on the other hand constexpr doesn't require compile time evaluation.

1

u/bert8128 1d ago

Why is it clutter any more than const is clutter? It gives a benefit - it guarantees that it can be called at compile time and if you you change the code so this is no longer true then you get a compile error.
Constexpr also guarantees no UB.

3

u/oriolid 1d ago

If constexpr actually required that the function would have no side effects, no UB, etc that would be really useful. Unfortunately it does not except in constexpr context and only for the exact arguments it was called with.

1

u/HappyFruitTree 1d ago

You only have these guarantees for the set of argument values that you actually call the function with from constexpr contexts.

int bar(int x) {
    return x;
}

constexpr int foo(int x) {
    if (x > 0)
        return bar(x); 
    return 0;
}

int x = foo(5); // OK
constexpr int y = foo(0); // OK
constexpr int z = foo(5); // Error

3

u/etaithespeedcuber 1d ago

One issue is that C++ sucks and you can't write exposed constexpr functions in source files, only in headeds. Though realistically if you do something like: foo.hpp size_t resolve_size(); foo.cpp ```

include "foo.hpp"

size_t resolve_size() { constexpr size_t result = resolve_size_constexpr_impl(); return result; } ``` The compiler will probably output exactly the same thing. But you still won't be able to use it in other constexpr functions

1

u/bert8128 1d ago

So if we narrow the scope to only those defined in the header is there a downside?

1

u/etaithespeedcuber 1d ago

I mean, I think at that point it's just the limitations of the language. For example, you can't have a constexpr function that returns an object that is used directly at runtime if that object depends on the heap

2

u/HappyFruitTree 1d ago

You can, you just won't be able to call it from constexpr contexts (at compile time).

The following compiles and runs just fine (since C++20).

#include <vector>
#include <iostream>

constexpr std::vector<int> f() {
    std::vector<int> vec = {1, 2, 3};
    return vec;
}

int main() {
    std::vector<int> vec = f();
    for (int n : vec) {
        std::cout << n << "\n";
    }
}

https://godbolt.org/z/WzfxYnefG

1

u/etaithespeedcuber 1d ago

How does it work? Does it convert it to an underlying static std::span? Or does it just literally run the function at runtime?

2

u/HappyFruitTree 1d ago

Yes, vec in main is not declared constexpr so it will basically just compile and optimize it the same way as a normal function.

2

u/meancoot 1d ago

Evaluates as if done at run time. This is always the case for constexpr functions, unless they are used in a context which requires a constant expression they behave as if executed at runtime. This is observable when you add C++20's std::is_constant_evaluated into the mix.

https://godbolt.org/z/Ynxo6Evx1

#include <type_traits>

constexpr bool constexpr_function() {
    return std::is_constant_evaluated();
}

int main() {
    constexpr bool result = constexpr_function(); // <- result = true
    // bool result = constexpr_function(); // <- result = false
    return result;
}

consteval works around this by requiring a marked function to always execute at compile time, but the all of the arguments to such functions are required to be constant expressions.

2

u/HappyFruitTree 20h ago edited 19h ago

Constant evaluation can also happen when initializing static variables and local const integer variables (if constant evaluation fails it falls back to normal "runtime" evaluation). https://godbolt.org/z/qE8PEq18r

4

u/Nils_Meul 1d ago

I don’t think there’s any downside to putting it everywhere it can go. Of course if you start making all your code header only just to take advantage of constexpr that’s a different question.

2

u/HappyFruitTree 1d ago

Even on functions that can not be executed in constexpr contexts?

int bar(int x);

constexpr int foo(int x)
{
    return bar(x);
}

2

u/Nils_Meul 1d ago

That’s the thing, in this case constexpr just doesn’t do anything (to my knowledge). The compiler isn’t forced to evaluate it at compile time even if it could, other than with constexpr variables…

5

u/HappyFruitTree 1d ago

Then why add it?

My main concern is that it can be confusing for the programmer (code is not only written for compilers). If I see a function marked constexpr I normally assume that it can result in a constant expression for at least some arguments.

That constexpr functions had to be able to produce a constant expression for at least one set of argument values was actually a requirement before C++23 (no diagnostic required).

3

u/Nils_Meul 1d ago

True, valid point. But I was thinking more so about putting it wherever it can in principle run at compile time, and if you mistakenly put it somewhere it can’t that’s of course not optimal but not terrible either.

1

u/oriolid 1d ago

Just have a look at this: https://godbolt.org/z/8Gsd13sv8. In C++11 std::sin isn't constexpr, and wrap_sin not constexpr just to make sure. It still gets evaluated at compile time.

3

u/Nils_Meul 1d ago

Sure, the compiler is always allowed to evaluate something at compile-time. But if you want to assign the value of sin(1.0) to a constexpr variable you‘ll need a constexpr std::sin (at least I‘m pretty sure that you do) even though the compiler can figure out it’s value as shown in your example.

1

u/oriolid 1d ago

I know. The example was chosen to show that *not* adding constexpr does not prevent compiler from compile time evaluation. If you actually need the result in constexpr context, then you of course need to have the funtion as constexpr.

So, putting it wherever ever you can does not really do anything useful. There are more fun ways to waste time.

1

u/Slow_Negotiation_935 1d ago

The code was already header only.

2

u/SyntheticDuckFlavour 1d ago

My general rule is, if you create reusable libraries where it may be applied in, or make sense in a constexpr context, then invest in making your functions constexpr. For example, make your math libraries constexpr wherever possible. For objects and APIs whose role is mutating state, don't bother. For example, state machines in a game, or something.

1

u/Normal-Narwhal0xFF 1d ago

I don't think constexpr changes a lot of the ways one would optimize. First, measure and find the code that's taking the time. Optimizing other code will keep you busy but not give results. Only focus on observed hot spots. Then decide if there's values being computed repeatedly that never change. If so they may benefit from constexpr. If code isn't inlining, and you think it'd help the hot spots, maybe constexpr can help there too. But make sure you're actually getting compile time resolution. Constexpr functions can still be runtime if they aren't used in constant expression context. So ensure it's giving you benefit of else it's just messing around. Measure, observe, and measure again. Without good data and feedback, you won't get results and will mostly waste time and even possibly introduce bugs with needless change.

1

u/RazzmatazzLatter8345 8h ago

Numerical types (particularly without heap allocation) is where I would especially try to make all operations constexpr. Even with heap allocation constexpr may help, it you constexpr the dtor and your version of C++ supports it. (Of course such allocations gotta be deleted at compile time too). constexpering all the things is awesome. Embrace it, as I have!

1

u/New-Step3500 1d ago edited 1d ago

I made a video on the constexpr . I delve what it is and when/how to properly use the keyword Constexpr c++

-1

u/zerhud 1d ago

Cpp believes you should write a constexpr keywords everywhere for some reason. May be it likes the keyword more others.

4

u/HappyFruitTree 1d ago

Who is Cpp?

1

u/Slow_Negotiation_935 1d ago

You are number 6…

1

u/zerhud 1d ago

It’s a creature from Mars pretending to be a programming language

1

u/crowbarous 1d ago

Yes, it was weird to adopt constexpr as a keyword that you can use almost always on a (non-opaque) function, and then make you actually write the keyword rather than implying it.

-fimplicit-constexpr exists in gcc, but only works on inline functions. Which kind of makes sense, but unfortunately I don't see a corresponding -fimplicit-inline to truly fix the anti-C++ design of meaninglessly requiring keywords just to allow mechanisms that are already there to do their job.

There have been questions about implementing that in Clang, but it doesn't seem to have been met with much enthusiasm. A libc++ maintainer even threatened to break libc++ if this option were added to clang. (WTF...)

2

u/HappyFruitTree 1d ago edited 1d ago

When constexpr functions were introduced in C++11 there were many more limitations. There could basically just be a single return statement, no variables, no loops, no if statements...

I do agree with the concerns of the guy that you linked to. People need to know that the constexpr is intentional if they should be able to rely on it.