33

Given a very trivial function,

int transform(int val) {
    return (val + 7) / 8;
}

It should be very obvious that it's easy to turn this function into a constexpr function, allowing me to use it when defining constexpr variables, like so:

constexpr int transform(int val) {
    return (val + 7) / 8;
}

My assumption is that this is strictly an improvement, since the function can still be called in a non-constexpr context, and it can now also be used to help define compile-time constant variables.

My question is, are there situations where this is a bad idea? Like, by making this function constexpr, can I ever encounter a situation where this function will no longer be usable in a particular circumstance, or where it will misbehave?

1
  • 1
    The only thing I could think of was compiler bugs. It's possible that a recursive constexpr function call might cause a really slow compile step or even a compiler out of memory crash.
    – Zan Lynx
    Apr 11, 2017 at 19:59

2 Answers 2

28

This matters only if the function is part of a public interface, and you want to keep future versions of your API binary-compatible. In that case, you have to think carefully how you want to evolve your API, and where you need extension points for future changes.

That makes a constexpr qualifier an irrevocable design decision. You cannot remove this qualifier without an incompatible change to your API. It also limits how you can implement that function, e.g. you would not be able to do any logging within this function. Not every trivial function will stay trivial in eternity.

That means you should preferably use constexpr for functions that are inherently pure functions, and that would be actually useful at compile time (e.g. for template metaprogramming). It would not be good to make functions constexpr just because the current implementation happens to be constexpr-able.

Where compile-time evaluation is not necessary, using inline functions or functions with internal linkage would seem more appropriate that constexpr. All of these variants have in common that the function body is “public” and is available in the same compilation unit as the call location.

If the function in question is not part of a stable, public API, this is less of an issue since you can arbitrarily change the design at will. But since you now control all call sites, it is not necessary to mark a function constexpr “just in case”. You know whether you are using this function in a constexpr context. Adding unnecessarily restrictive qualifiers might then be considered obfuscation.

3
  • how does constexpr affect the ABI / binary-compat?
    – user98577
    Jan 26, 2020 at 2:07
  • @Mave TBH I'm not sure what I had in mind a couple of years ago, but the other answer mentions inline which can affect whether two compilation units can link. Or maybe I was hinting that constexpr/template oriented APIs are more difficult to evolve in a backwards compatible manner than e.g. pImpl oriented APIs?
    – amon
    Jan 26, 2020 at 12:16
  • 1
    The logging isn't an issue anymore in C++20. Since you have std::is_constant_evaluated, which allows you to detect whether a constexpr function is being called in a constexpr context. This allows you to log only when the function is being called at runtime. Or in C++23, you can more succintly say if !consteval { /* ... */ }. Jul 19, 2021 at 4:29
16

Marking a function as constexpr also makes it an inline function §[dcl.constexpr]/1:

A function or static data member declared with the constexpr specifier is implicitly an inline function or variable (7.1.6).

inline, in turn, means you need include the definition of that function in every translation unit in which it may be used. That basically means constexpr functions have to be either:

  1. restricted to use in one translation unit, or
  2. defined in a header.

Most typical functions that you want to declare in a header and define in a source file (and anything else that uses them just includes the header, then links against that source's object file) constexpr simply won't work.

In theory, I suppose you could just move everything into headers and have only one source file that just includes all the headers, but this would hurt compile times drastically, and for most serious projects would require immense amounts of memory to compile.

A constexpr function is also restricted in some ways, so for some functions it may not be an option at all. The restrictions include:

  1. virtual functions can't be constexpr.
  2. its return type must be a 'literal type" (e.g., no objects with non-trival ctors or dtors).
  3. all of its parameters must be literal types.
  4. the function body can't contain a try block.
  5. it can't contain a variable definition of a non-literal type, or anything with static or thread storage duration.

I've skipped over a couple rather obscure things (e.g., it also can't contain a goto or an asm statement), but you get the idea--for quite a few things, it just won't work.

Bottom line: yes, there are quite a few situations where this would be a poor idea.

1
  • 1
    "it must not be virtual (until C++20)" I'm wondering how a virtual function can be constexpr? What do compilers do?
    – chaosink
    Jul 30, 2019 at 19:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.