With the new C++11 standard, when should I use the inline keyword over the constexpr keyword? Does the constexpr keyword offer any additional optimization over inline, or does it merely assert that things must be computed at compile-time?
Why does constexpr work on the GCC in some cases where the call is not constant, such as calling foo(x) on a non-constexpr variable? Is this a bug in the GCC or is it actually part of the standard?
Asserting that something can be computed at compile-time is a pretty strong kind of optimization.
Inlining merely removes a function call, by copy/pasting the function body into the call site. The function body still has to be executed, you just save the overhead of a function call.
But if you make the same code be evaluated at compile-time, it is free at runtime.
But neither inline nor constexpr are primarily about optimization. inline's main purpose is to suppress the one-definition-rule, so that functions can be defined in headers (which is useful for templates, and incidentally, also makes the inlining optimization easier)
And constexpr is there because it is useful in metaprogramming, and incidentally, it may help the compiler better optimize the code, by moving more computations to compile-time.
To quote wikipedia:
C++0x will introduce the keyword constexpr, which allows the user to
guarantee that a function or object constructor is a compile-time
constant.
Mark functions inline if they are super short. Mark functions as constexpr if the results are required at compile time. (Template parameters or array sizes). I believe a function can be both if needed.
A constant expression function or constructor can be called with
non-constexpr parameters. Just as a constexpr integer literal can be
assigned to a non-constexpr variable, so too can a constexpr function
be called with non-constexpr parameters, and the results stored in
non-constexpr variables. The keyword only allows for the possibility
of compile-time constancy when all members of an expression are
constexpr.
So, GCC is not incorrect in this.
While inline says to the compiler "This function is used somewhere in this translation unit and is not public to other object files", it is likely that the compiler inserts the body of the function into the caller. constexpr functions say to the compiler "This function has no side effects and does not depend on preconditions other than the parameter itsself."
constexpr variables just say "This variable does not change and its data can be included into the code.". However it makes a difference if you define a constexpr variable in a function static or nonstatic, eg. if a constexpr array is nonstatic, gcc just moves the data with hardcoded mov-instructions onto the stack, while static constexpr just stores the data in the .text-section.
Lambda expressions without capture assigned to a variable can be constexpr other than with capture, because without they need no memory to save the capture and they work like an empty class with overloaded operator() (but they can even be casted to plain function pointers with a simple unary plus: +[]{}).
Related
cppreference said the following about the body of a constexpr function:
the function body must not contain:
a definition of a variable of non-literal type
a definition of a variable of static or thread storage duration.
All I understood about a constexpr function is that the statements in its body should be evaluated at compile-time so that the call expression can be evaluated at compile-time. Am I true?
Since C++14, the standard allows the body to contain variable definitions of literal types. So what about those definitions, are they evaluated at compile-time or runtime? since non-constexpr variables are allowed also.
What I think I misunderstood is that the compiler shall be able to evaluate at compile-time every statement in a constexpr function body. Does this true since C++14?
To make my confusion clear, I have this simple example:
// assuming std::is_literal_type<T> is true.
constexpr T f() { T t{}; return t; };
I can't understand how the compile behaves with the above snippet. Variable t is an automatic non-const variable and gets defined at run-time not compile-time; so it basically cannot appear in a constexpr declaration. Does this means, the compiler will never evaluate f() at compile-time because it has a statement, that's T t;, which will be evaluated at runtime only.
My confusion is increased when I have tried:
constexpr T result = f();
and it compiles successfully!. Does this mean f() are evaluated at compile-time? if yes, what about the runtime definition of t?
My second question is about why static-duration variables are not allowed in the body of a constexpr function.
the statements in its body should be evaluated at compile-time
...along at least one possible code path.
Or, as the cpprefenrce page you're quoting puts it,
there exists at least one set of argument values such that an invocation of the function could be an evaluated subexpression of a core constant expression
The function can be doing disk I/O if it wants to, as long as there is an if branch that skips it.
Variable t is ... defined at run-time not compile-time
It's defined in both. There is a run-time definition of the function (actual machine code compiled and written to the object file) and also there is a compile-time version of the function, a bunch of AST nodes or some sort of internal compiler representation of them. In a constexpr context, compile-time version will be evaluated and replaced by its result. In runtime context, a function call instruction will be compiled.
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:
Write a test case or otherwise ensure it's actually used in a constant expression.
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?
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 const 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 const 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
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)
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.
We can live without constexpr, but in certain cases it makes the code easier and intuitive.
For example we have a class which declares an array with some 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
constexprt int k = i; // error
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.
If a non-literal class type has no constexpr constructor (it is not constexpr constructible), does a non-static constexpr member function make any sense? I mean if you cannot construct the object at compile time, how would you able to use its member functions?
Anyway, the major compilers don't complain about it, which makes me think it is allowed by the standard.
Nevertheless, you are able to use such constexpr member functions in runtime without any problem. The only question now what is the effect of constexpr in this case, if any. My best guess is that the return value of the constexpr member is being evaluated at compile-time (if possible), so on a run-time call it have to do a simple copy.
Is my guess correct, or is the constexpr specifier absolutely meaningless in this case (i.e. the member function is being evaluated at runtime)?
The premise of your question seems to be that only constexpr functions can be evaluated at compile-time.
This premise is incorrect. The compiler can precompute anything it can figure out a way to do, as long as the exact side result and side-effects are produced (as-if rule).
What constexpr provides is a guarantee that certain expressions will be evaluated at compile-time by every compiler (it's not a "quality of implementation" issue), which makes it possible to use them in contexts where a compile-time value is needed, such as non-type template arguments, operands of case clauses in switch statements, etc.
The specific details around constexpr functions include that there has to be at least one set of arguments (the target instance is an implied argument) such that the constexpr evaluation rules are met. If that isn't true, your program is ill-formed and its runtime behavior is not specified at all, so don't go adding constexpr where it doesn't logically belong.
However, compilers aren't required to diagnose violations of this rule. That means that "major compilers don't complain about it" should not be in any way interpreted as assurance that the code is correct.
Standard's wording, section 7.1.5 (draft n4582)
For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of
a core constant expression, or, for a constructor, a constant initializer for some object, the program is ill-formed; no diagnostic required.
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:
Write a test case or otherwise ensure it's actually used in a constant expression.
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?
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 const 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 const 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
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)
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.
We can live without constexpr, but in certain cases it makes the code easier and intuitive.
For example we have a class which declares an array with some 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
constexprt int k = i; // error
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.
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:
Write a test case or otherwise ensure it's actually used in a constant expression.
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?
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 const 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 const 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
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)
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.
We can live without constexpr, but in certain cases it makes the code easier and intuitive.
For example we have a class which declares an array with some 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
constexprt int k = i; // error
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.