Whyever **not** declare a function to be `constexpr`?
Asked Answered
B

3

65

Any function that consists of a return statement only could be declared constexpr and thus will allow to be evaluated at compile time if all arguments are constexpr and only constexpr functions are called in its body. Is there any reason not to declare any such function constexpr ?

Example:

  constexpr int sum(int x, int y) { return x + y; }
  constexpr i = 10;
  static_assert(sum(i, 13) == 23, "sum correct");

Could anyone provide an example where declaring a function constexpr would do any harm?


Some initial thoughts:

Even if there should be no good reason for ever declaring a function not constexpr I could imagine that the constexpr keyword has a transitional role: its absence in code that does not need compile-time evaluations would allow compilers that do not implement compile-time evaluations still to compile that code (but to fail reliably on code that needs them as made explict by using constexpr).

But what I do not understand: if there should be no good reason for ever declaring a function not constexpr, why is not every function in the standard library declared constexpr? (You cannot argue that it is not done yet because there was not sufficient time yet to do it, because doing it for all is a no-brainer -- contrary to deciding for every single function if to make it constexpr or not.) --- I am aware that N2976 deliberately not requires cstrs for many standard library types such as the containers as this would be too limitating for possible implementations. Lets exclude them from the argument and just wonder: once a type in the standard library actually has a constexpr cstr, why is not every function operating on it declared constexpr?

In most cases you also cannot argue that you may prefer not to declare a function constexpr simply because you do not envisage any compile-time usage: because if others evtl. will use your code, they may see such a use that you do not. (But granted for type trait types and stuff alike, of course.)

So I guess there must be a good reason and a good example for deliberately not declaring a function constexpr?

(with "every function" I always mean: every function that meets the requirements for being constexpr, i.e., is defined as a single return statement, takes only arguments of types with constexpr cstrs and calls only constexpr functions. Since C++14, much more is allowed in the body of such function: e.g., C++14 constexpr functions may use local variables and loops, so an even wider class of functions could be declared constexpr.)

The question Why does std::forward discard constexpr-ness? is a special case of this one.

Berners answered 25/2, 2011 at 0:34 Comment(4)
My question is: what will happen to a function declared as constexpr but doesn't result in a constant expression when invoked, it shall be compilation error isn't it? So for those functions that are not particularly aimed to be evaluated only at compile time, they shouldn't be declared as constexpr?Sochi
I searched the standard and couldn't find a hint on what will happen if a constexpr function is called by non-const expression arguments. Anyway, if this is error, then the std::forward case is clear too, if you define std::forward constexpr, then it must be used as constexpr and can't forward normal variables.Sochi
@Sochi I also cannot find the spot in the standard. But the only thing making sense (and what g++ actually does) is silently ignoring constexpr when a constexpr function is called with non-constexp arguments. Otherwise functions like size in std::bitset obviously would make no sense to be constexpr.Berners
@Lars: Not only that, I also noticed that g++ ignores constexpr if the output is not explicitly constexpr, regardless of whether the inputs are. Though I'm not sure if that is what's intended by the standard, it does not make any sense to me. For example, assigning a constexpr function's return value to a const int will cause the function to appear in the binary and be executed, whereas assigning it to an enum (with the same inputs!) just defines an enumeration value and generates no code at all.Triplicate
I
38

Functions can only be declared constexpr if they obey the rules for constexpr --- no dynamic casts, no memory allocation, no calls to non-constexpr functions, etc.

Declaring a function in the standard library as constexpr requires that ALL implementations obey those rules.

Firstly, this requires checking for each function that it can be implemented as constexpr, which is a long job.

Secondly, this is a big constraint on the implementations, and will outlaw many debugging implementations. It is therefore only worth it if the benefits outweigh the costs, or the requirements are sufficiently tight that the implementation pretty much has to obey the constexpr rules anyway. Making this evaluation for each function is again a long job.

Insider answered 25/2, 2011 at 10:59 Comment(7)
It was not my intention to criticize the job of the library working group, sorry if my question should have sounded like that. What you say relates to interfaces, and you are right, the standard library describes such an interface with many implementations. So evtl. looking into the STL for guidance of constexpr usage was not clever by me. My question is about implementations. Would you agree that implementations should be constexpr whenever possible? Or are there examples for implementation not being constexpr? (Sorry, evtl. I did not get your point...)Berners
Making something constexpr is quite constraining. It is not something I would do lightly, as once it becomes part of your interface it is hard to change it due to backwards compatibility concerns. Even for a given implementation of the standard library this applies --- many people will unwittingly rely on implementation properties of the libraries they use, so I would not expect implementations to make any functions constexpr apart from those required by the standard, or which there is no reasonable implementation which would violate the constraints.Insider
Not sure how things were at the time this answer was written, but constexpr doesn't disqualify a function from having dynamic_cast or new. The constant-ness of an expression cannot be determined while it has unbound parameters, so there's no requirement that the return expression be constant except as a potentiality. Since C++ compiler analysis doesn't deal in potentialities, there is no diagnostic required, i.e. nothing is verified.Escharotic
You are right that the C++ Standard says "ill-formed; no diagnostic required" for violation of the constexpr requirements in the function definition. That doesn't make it OK --- it's still ill-formed if it cannot be part of a constant expression. If the non-constant-expression-suitable parts are dependent on the values of function parameters then it's only a hard error if you try and use it in a constant expression. Likewise for templates.Insider
Hi! Sorry, still not understanding, could you give a code example? If we declare constexpr in a function and this may be or not constexpr after compiler evaluation, then why not declare it in every function that may compile this way (if we want to run more in compile time than in runtime)?Placida
Firstly, constexpr functions are implicitly inline, so if you don't want to put your function definitions in your header files, they can't be constexpr. Secondly, declaring a function constexpr means you cannot use try blocks, static variables, thread_local variables, or variables of non-literal types, so no std::string or std::vector, etc.Insider
If you have an inline function that could be constexpr, there is no downside to declaring it as such, except that it might constrain you for the future --- changing the implementation to not be constexpr-compatible would then be an ABI break, as somewhere in your code it might be used in a constant expression.Insider
S
15

I think what you're referring to is called partial evaluation. What you're touching on is that some programs can be split into two parts - a piece that requires runtime information, and a piece that can be done without any runtime information - and that in theory you could just fully evaluate the part of the program that doesn't need any runtime information before you even start running the program. There are some programming languages that do this. For example, the D programming language has an interpreter built into the compiler that lets you execute code at compile-time, provided that it meets certain restrictions.

There are a few main challenges in getting partial evaluation working. First, it dramatically complicates the logic of the compiler because the compiler will need to have the ability to simulate all of the operations that you could put into an executable program at compile-time. This, in the worst case, requires you to have a full interpreter inside of the compiler, making a difficult problem (writing a good C++ compiler) and making it orders of magnitude harder to do.

I believe that the reason for the current specification about constexpr is simply to limit the complexity of compilers. The cases it's limited to are fairly simple to check. There's no need to implement loops in the compiler (which could cause a whole other slew of problems, like what happens if you get an infinite loop inside the compiler). It also avoids the compiler potentially having to evaluate statements that could cause segfaults at runtime, such as following a bad pointer.

Another consideration to keep in mind is that some functions have side-effects, such as reading from cin or opening a network connection. Functions like these fundamentally can't be optimized at compile-time, since doing so would require knowledge only available at runtime.

To summarize, there's no theoretical reason you couldn't partially evaluate C++ programs at compile-time. In fact, people do this all the time. Optimizing compilers, for example, are essentially programs that try to do this as much as possible. Template metaprogramming is one instance where C++ programmers try to execute code inside the compiler, and it's possible to do some great things with templates partially because the rules for templates form a functional language, which the compiler has an easier time implementing. Moreover, if you think of the tradeoff between compiler author hours and programming hours, template metaprogramming shows that if you're okay making programmers bend over backwards to get what they want, you can build a pretty weak language (the template system) and keep the language complexity simple. (I say "weak" as in "not particularly expressive," not "weak" in the computability theory sense).

Hope this helps!

Sarcoma answered 25/2, 2011 at 4:29 Comment(6)
The rules don't appear to forbid side-effects.Soriano
While a very interesting answer (partial evaluation is a big topic, and every compiler writer has tried it a bit), it seems a bit off-topic, since the question is more about the use of constexpr (for the programmer) than its effect, it seems.Mammal
@Matthieu M.- I may have misread the question. My interpretation of the question is "why not design constexpr so that any function can be constexpr and the compiler will give an error if it can't do it at compile-time?" In that sense, I think this answer is appropriate. If I'm way off from the OP's original question, I can delete this post.Sarcoma
I'd rather you leave this post, it's interesting. I didn't know that D embedded an interpreter for example :) I must admit I am a bit fuzzy on the requirements for constexpr, I'll need to work on it.Mammal
@Matthieu M. Thanks for the information. For me, too, the things about D were new and interesting. But Matthieu is right, you answer the question: why is constexpr limited to functions consisting of a single return statement? While my question is: will we now have to declare every function constexpr or are there any cases where this would have negative effects? (But please leave your answer!)Berners
This is a very useful answer, and I agree on your point that TMP provides a very nice pure functional language. Although, constexpr allows writing pure functional code as well, while providing a nice bridge between runtime and compile-time variables. However, not having functions like std::find and some others defined constexpr seems to artificially limit the kinds of pure code one can write using the STL.Stephens
S
4

If the function has side effects, you would not want to mark it constexpr. Example

I can't get any unexpected results from that, actually it looks like gcc 4.5.1 just ignores constexpr

Soriano answered 25/2, 2011 at 5:15 Comment(5)
In your example, f calls operator++ on a non-constexpr variable g, and thus is not a valid constexpr function. g++ (snapshot 2011-02-19) correctly throws an error message: "g is not a constant expression".Berners
@Lars: I read the [dcl.constexpr] section of the standard (draft 3225) several times and couldn't find anything that prevents this function from being constexpr, since the text "expression is a potential constant expression" got removed.Soriano
"Example" link is dead, ideone says "Solution not found"Allround
@AlexanderMalakhov: Report it to ideone. Their FAQ guarantees that code is available "forever": ideone.com/faq I no longer use that site exactly because they delete content, without warning, and in violation of their own terms.Soriano
I didn't mean to say it's your fault :) But maybe you remember that code and could reproduce it here?Allround

© 2022 - 2024 — McMap. All rights reserved.