55

C++11 allows functions declared with the constexpr specifier to be used in constant expressions such as template arguments. There are stringent requirements about what is allowed to be constexpr; essentially such a function encapsulates only one subexpression and nothing else. (Edit: this is relaxed in C++14 but the question stands.)

Why require the keyword at all? What is gained?

It does help in revealing the intent of an interface, but it doesn't validate that intent, by guaranteeing that a function is usable in constant expressions. After writing a constexpr function, a programmer must still:

  1. Write a test case or otherwise ensure it's actually used in a constant expression.
  2. Document what parameter values are valid in a constant expression context.

Contrary to revealing intent, decorating functions with constexpr may add a false sense of security since tangential syntactic constraints are checked while ignoring the central semantic constraint.


In short: Would there be any undesirable effect on the language if constexpr in function declarations were merely optional? Or would there be any effect at all on any valid program?

Community
  • 1
  • 1
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 1
    Why exactly are test cases required to ensure the `constexpr` function is used as such? The function can be used in contexts that require it to be `constexpr` as well as other contexts. Why is that a problem, and how exactly does the test help? – jogojapan Jan 23 '13 at 04:30
  • 4
    @jogojapan The test case ensures that you don't have a `constexpr` function that cannot be used in the intended constant context. Such a bug is bound to confuse the user of the interface, wasting their time etc, and can mask a serious design flaw. – Potatoswatter Jan 23 '13 at 04:38
  • 2
    If you actually use such a function in the wrong way (e.g. call `f(false)` as defined in the linked question from code that requires a constant expression), the compiler will tell you (i.e. at compile time). How does having a test improve anything? – jogojapan Jan 23 '13 at 04:42
  • 6
    @jogojapan: It will tell you that the function really is `constexpr` when you want it to be so you don't end up with surprising results later when someone tries to use it. Just like all tests, it's used to make sure that the code works according to the documentation. – Omnifarious Jan 23 '13 at 04:47
  • 1
    @jogojapan Any test case has to accurately reflect the desired semantics… that's not a reason not to test. – Potatoswatter Jan 23 '13 at 04:48
  • I tried, I really did. **What is your question?** – Lightness Races in Orbit Jan 23 '13 at 04:49
  • 1
    @Non-StopTimeTravel TL;DR: Would there be any effect at all on the language if `constexpr` in function declarations were merely optional? (Semantic effect on valid programs, or undesirable effect on invalid programs.) – Potatoswatter Jan 23 '13 at 04:51
  • 1
    This is a really good question. With the rule relaxed so a function may not actually be a constant expression if it's called with the wrong argument, why doesn't the compiler just treat all functions that return the result of a single expression as potentially `constexpr`? – Omnifarious Jan 23 '13 at 04:51
  • 1
    @Potatoswatter Ok. I guess you have a point there. – jogojapan Jan 23 '13 at 04:53
  • 1
    +1. Good points. `constexpr` functions require programmers to think about compilers and how it could evaluate the function when writing the function itself. It is not only about flow and logic, but also about **all possible** compile-time *values* and the *paths* that it could follow. Thinking about all possible constant values is really a pain. It is like this : `int a[f(10)];` works, but `int a[f(11)];` fails to compile, because it takes a different path which cannot be executed at compile-time. That is indeed confusing to users. – Nawaz Jan 23 '13 at 05:23
  • 3
    I think the question has much in common with the question "Why do compilers reject ill-formed templates even before i try to instantiate them?". There are both fail-fast checks. – Johannes Schaub - litb Jan 23 '13 at 18:02

4 Answers4

46

Preventing client code expecting more than you're promising

Say I'm writing a library and have a function in there that currently returns a constant:

awesome_lib.hpp:

inline int f() { return 4; }

If constexpr wasn't required, you - as the author of client code - might go away and do something like this:

client_app.cpp:

#include <awesome_lib.hpp>
#include <array>

std::array<int, f()> my_array;   // needs CT template arg
int my_c_array[f()];             // needs CT array dimension

Then should I change f() to say return the value from a config file, your client code would break, but I'd have no idea that I'd risked breaking your code. Indeed, it might be only when you have some production issue and go to recompile that you find this additional issue frustrating your rebuilding.

By changing only the implementation of f(), I'd have effectively changed the usage that could be made of the interface.

Instead, C++11 onwards provide constexpr so I can denote that client code can have a reasonable expectation of the function remaining a constexpr, and use it as such. I'm aware of and endorsing such usage as part of my interface. Just as in C++03, the compiler continues to guarantee client code isn't built to depend on other non-constexpr functions to prevent the "unwanted/unknown dependency" scenario above; that's more than documentation - it's compile time enforcement.

It's noteworthy that this continues the C++ trend of offering better alternatives for traditional uses of preprocessor macros (consider #define F 4, and how the client programmer knows whether the lib programmer considers it fair game to change to say #define F config["f"]), with their well-known "evils" such as being outside the language's namespace/class scoping system.

Why isn't there a diagnostic for "obviously" never-const functions?

I think the confusion here is due to constexpr not proactively ensuring there is any set of arguments for which the result is actually compile-time const: rather, it requires the programmer to take responsibility for that (otherwise §7.1.5/5 in the Standard deems the program ill-formed but doesn't require the compiler to issue a diagnostic). Yes, that's unfortunate, but it doesn't remove the above utility of constexpr.

So, perhaps it's helpful to switch from the question "what's the point of constexpr" to consider "why can I compile a constexpr function that can never actually return a compile-time value?".

Answer: because there'd be a need for exhaustive branch analysis that could involve any number of combinations. It could be excessively costly in compile time and/or memory - even beyond the capability of any imaginable hardware - to diagnose. Further, even when it is practical having to diagnose such cases accurately is a whole new can of worms for compiler writers (who have better uses for their time). There would also be implications for the program such as the definition of functions called from within the constexpr function needing to be visible when the validation was performed (and functions that function calls etc.).

Meanwhile, lack of constexpr continues to forbid use as a compile-time value: the strictness is on the sans-constexpr side. That's useful as illustrated above.

Comparison with non-`const` member functions

  • constexpr prevents int x[f()] while lack of const prevents const X x; x.f(); - they're both ensuring client code doesn't hardcode unwanted dependency/usage

  • in both cases, you wouldn't want the compiler to determine const[expr]-ness automatically:

    • you wouldn't want client code to call a member function on a const object when you can already anticipate that function will evolve to modify the observable value, breaking the client code

    • you wouldn't want a value used as a template parameter or array dimension if you already anticipated it later being determined at runtime

  • they differ in that the compiler enforces const use of other members within a const member function, but does not enforce a compile-time constant result with constexpr (due to practical compiler limitations, and perhaps so that one function definition can happily serve up run-time results for run-time-known arguments/values, but will return a compile-time result when possible and client usage requires).

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • 8
    Yes, this is about "revealing the intent." But the intent isn't guaranteed. You can still fetch the value from the config file without removing the `constexpr`. Such issues of intent are ultimately best left to documentation. – Potatoswatter Jan 23 '13 at 04:42
  • 2
    @Potatoswatter: but it's not just documentation because it's enforced in the client code where a const expression is needed: they can't build client usage at odds with your "documented" intent. I'm not saying that's thorough, but it's still valuable. In general, having the compiler check the `constexpr` implementation before seeing client usage would create new restrictions on the programmer - e.g. functions called inside the `constexpr` function must be defined and not just declared before that check's done. – Tony Delroy Jan 23 '13 at 05:07
  • 1
    Client code doesn't enforce things. (It may do nothing, and generally it's not accessible to the library author.) Ah… I hadn't thought about that restriction on testing, so my style guide suggestion might not apply to 100% of cases. – Potatoswatter Jan 23 '13 at 05:10
  • 1
    "Client code doesn't enforce things" - by "[constexpr] is enforced *in* the client code" I mean that if you use a non-`constexpr` value for that array dimension then it won't compile: that's enforcement consistent with the utility in my answer. For "compiler check...implementation" I mean situations like `[constexpr?] int f() { return rand(); }` - to ensure that's const you must know the definition of `rand`. – Tony Delroy Jan 23 '13 at 05:15
  • 4
    Re the edit… to be clear about the issue, you can define `constexpr int f() { return fetch_config( "bob's high score" ); }` and the compiler won't complain until it's used in `my_array[f()];`. So when you write `constexpr`, you also have to both document *when* it's a constant, and `static_assert` that the documentation is correct. Having done all that, `constexpr` is redundant, and the `static_assert` is the inline documentation that the user should *really* be looking at. – Potatoswatter Jan 23 '13 at 05:36
  • 2
    @Potatoswatter: a static assert can help guarantee a function returns a const result where that's the author's intention, but if you're writing a function that isn't intended to necessarily return a const value but happens to right now, you're not going to want to code a static assert and client code can still couple to it. `constexpr` communicates the appropriateness of this in the actual interface and prevents this unwanted coupling. – Tony Delroy Jan 23 '13 at 06:04
  • 2
    "I think the confusion here is due to constexpr not ensuring there are any arguments for which the result is actually compile-time const" -- this part is wrong, there must always be atleast *one* path that produces a constant expression. – Xeo Jan 23 '13 at 08:59
  • 2
    @Xeo: §7.1.5/5 "For a constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19), the program is ill-formed; no diagnostic required." (as per Potatoswatter's answer - follow link in this question) - so "constexpr not ensuring..." is correct - it's still the programmer's responsibility. – Tony Delroy Jan 23 '13 at 09:15
  • 3
    No diagnostic is required because emitting the diagnostic in all possible cases would require solving the halting problem. – Sebastian Redl Jan 23 '13 at 12:48
  • 3
    @SebastianRedl: While true, that doesn't change the case that the compiler does not confirm that there is a set of arguments that result in a compile-time const function. `constexpr` doesn't guarantee it, because the compiler _cannot_ guarantee it. The spec has that rule, but it cannot be enforced, so isn't an actual guarantee. – Mooing Duck Jan 23 '13 at 21:33
19

When I pressed Richard Smith, a Clang author, he explained:

The constexpr keyword does have utility.

It affects when a function template specialization is instantiated (constexpr function template specializations may need to be instantiated if they're called in unevaluated contexts; the same is not true for non-constexpr functions since a call to one can never be part of a constant expression). If we removed the meaning of the keyword, we'd have to instantiate a bunch more specializations early, just in case the call happens to be a constant expression.

It reduces compilation time, by limiting the set of function calls that implementations are required to try evaluating during translation. (This matters for contexts where implementations are required to try constant expression evaluation, but it's not an error if such evaluation fails -- in particular, the initializers of objects of static storage duration.)

This all didn't seem convincing at first, but if you work through the details, things do unravel without constexpr. A function need not be instantiated until it is ODR-used, which essentially means used at runtime. What is special about constexpr functions is that they can violate this rule and require instantiation anyway.

Function instantiation is a recursive procedure. Instantiating a function results in instantiation of the functions and classes it uses, regardless of the arguments to any particular call.

If something went wrong while instantiating this dependency tree (potentially at significant expense), it would be difficult to swallow the error. Furthermore, class template instantiation can have runtime side-effects.

Given an argument-dependent compile-time function call in a function signature, overload resolution may incur instantiation of function definitions merely auxiliary to the ones in the overload set, including the functions that don't even get called. Such instantiations may have side effects including ill-formedness and runtime behavior.

It's a corner case to be sure, but bad things can happen if you don't require people to opt-in to constexpr functions.

Community
  • 1
  • 1
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 1
    +1 but I still think adding the full answer including the second part `It is also useful as a statement of intent:` would be the complete answer. – Jesse Good Nov 07 '13 at 12:11
  • 1
    @JesseGood I pretty much refuted the statement of intent part in the question, so it wouldn't be fair to quote his answer from another context as a "rebuttal." – Potatoswatter Nov 07 '13 at 12:59
  • 2
    Any examples? I don't understand. If the function is not used in compile time expression, it doesn't need to be instantiated. – anton_rh Sep 07 '19 at 13:12
3

Without the keyword, the compiler cannot diagnose mistakes. The compiler would not be able to tell you that the function is an invalid syntactically as aconstexpr. Although you said this provides a "false sense of security", I believe it is better to pick up these errors as early as possible.

Jesse Good
  • 50,901
  • 14
  • 124
  • 166
  • 5
    It can't and doesn't tell you that the function is invalid as a `constexpr` even with the keyword. Once you write the requisite testcase, then it can tell you without the keyword. – Potatoswatter Jan 23 '13 at 04:34
  • 1
    @Potatoswatter: I meant invalid in the syntactical sense, as you mentioned in your question: `compiler checks that tangential syntactic constraints` Without the keyword, the syntax could not be checked. (Although, now I see your point seems to be that this provides a false sense of security) – Jesse Good Jan 23 '13 at 04:38
  • Yep. Maybe I should add an example to the question… Don't have time now… – Potatoswatter Jan 23 '13 at 04:46
3

We can live without constexpr, but in certain cases it makes the code easier and intuitive. The following example shows a class declaring an array with a reference length:

template<typename T, size_t SIZE>
struct MyArray
{
  T a[SIZE];
};

Conventionally you might declare MyArray as:

int a1[100];
MyArray<decltype(*a1), sizeof(a1)/sizeof(decltype(a1[0]))> obj;

Now see how it goes with constexpr:

template<typename T, size_t SIZE>
constexpr
size_t getSize (const T (&a)[SIZE]) { return SIZE; }

int a1[100];
MyArray<decltype(*a1), getSize(a1)> obj;

In short, any function (e.g. getSize(a1)) can be used as template argument only if the compiler recognizes it as constexpr.

constexpr is also used to check the negative logic. It ensures that a given object is at compile time. Here is the reference link e.g.

int i = 5;
const int j = i; // ok, but `j` is not at compile time
constexpr int k = i; // error
gustafbstrom
  • 1,622
  • 4
  • 25
  • 44
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • 5
    If you remove `constexpr` from that example, a non-compliant compiler may still compile it. And the code wouldn't be any less clear. The question is why the keyword is required, not how it is used. – Potatoswatter Jan 23 '13 at 04:41
  • @Potatoswatter, well the g++ doesn't compile without `constexpr`. Also my answer is discusses "why" only and not "how" :). See the edit, I have added one more use case. – iammilind Jan 23 '13 at 04:42
  • The second case relates to the last paragraph of the question… please read… – Potatoswatter Jan 23 '13 at 04:44
  • 1
    Do you mean `decltype(*a1)` instead of `decltype(a1)`? – aschepler Jan 23 '13 at 04:45