C++ constexpr expression evaluation - c++

I have a question regarding following comment about conditional expressions used in constexpr functions:
A branch of a conditional expression that is not taken in a constexpr function is not evaluated. Source: conditional evaluation of functions
As already written in the source you can have constexpr functions like
constexpr int check(int i) {
return (0<=i && i<10) ? i : throw out_of_range();
}
and only the branch that is taken is evaluated. So far, so good. But why is this invalid in combination with templates. So let's take this basic example here:
template <int N>
constexpr int times(int y) {
return (N<0) ? 0 : y+times<N-1>(y);
}
times<5>(10);
The compilation fails because the template instantiation depth exceeds the maximum even though the false branch of the conditional is only taken 4 times. Then it should take the true branch and return 0. Of course it can be rewritten using enable_if or whatever but I only would like to know following things:
Is this statement not valid when it comes to subexpression type evaluation?
Why does this fail even though the statement above claims that the subexpression is not evaluated? I guess the types have to be evaluated anyway (e.g., to check if the requirement that both branches of the conditional are having the same type is fulfilled) and therefore it ends in an infinite template instantiation. Correct assumption?
Is there a place in the c++ standard that describes this behavior?

You're misunderstanding what it means to evaluate something. Evaluation is something that happens at execution time (even if that execution happens while the compiler is running).
Template instantiation is a static property of your code. If you write times<N-1> you are asking to instantiate that template. It doesn't matter if you call that function or not; you write the instantiation, so it gets instantiated.
This is why recursive metaprogramming usually handles the terminal case through a template specialization.
This is why if constexpr was added to C++17. Because it has the power to, not merely conditionally evaluate statements, but conditionally discard statements, making the other branch effectively not exist. Which is not something that ever existed before. This allows the other branch to contain code that would have been statically il-formed otherwise.
So this would work:
if constexpr(N < 0) return 0 else return y+times<N-1>(y);
The second clause would be discarded and thus not instantiated.
So the statement is correct; subexpressions are conditionally evaluated. You're just misinterpreting how it applies to your case.

Related

throw in constexpr function: do we need wrapping condition?

Basic idea is this: I have some constexpr function and I want to use throw to signal error and lazy compilation to avoid this error in normal flow:
template <size_t N>
auto constexpr find_elt(const std::array<int, N>& a, int k) {
for (size_t i = 0; i < N; ++i)
if (k == a[i])
return i;
throw "not found";
}
And then:
constexpr int result = find_elt(arr, 4);
Normally, if 4 exists in the array, I will get its index back at compile-time.
If not, I will fall through to throw to indicate the lookup is erroneous at compile-time, and the compiler will produce a pretty error.
But I noticed strange behavior:
Under the latest clang, everything works
Under the latest gcc, everything fails
Is this idea legitimate? Is this code correct for what I want to achieve? Which compiler tells me the truth here?
If not, what is the correct way to do this?
Any links to C++ standard are appreciated. I read through constexpr-related chapters, but I am in doubt.
So:
according to the fact that the compilation of constexpr functions is a "lazy" process, then the check for compliance with the requirements for a constexpr function is performed only in the scope where the compiler still entered during expression substitution.
a function is a scope, respectively - all the rules for constexpr must be observed in the entire body of a function in its first level, scope.
and since the expression "throw" is not a constant expression (as the gcc-10 compiler already tells us about it), the correctness is not observed.
The clang compiler is not as strict in this sense as gcc. Therefore, in this battle, in my opinion, gcc wins. He is more committed to the Standard.
On the other hand, if this is a "lazy" process, then why shouldn't it be lazy to the end. Well, you found the final return - so why check the correctness further?
In this sense, clang gets a point.
And in the end - what does the C++17 Standard say?
10.1.5 The constexpr specifier [dcl.constexpr]
"... if no argument values ​​exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (8.20), ..., the program is ill-formed, no diagnostic required.
Next, let's see what the "core constant expression" is:
8.20 Constant expressions [expr.const]
An expression is a the evaluation of, following the rules of the abstract machine (4.6), would evaluate one of the following expressions:
2.22 - a throw-expression (8.17)
And note that "no diagnostic required" and the compiler is not required to provide a detailed explanation of the reason for the failure.

Can the conditional operator ( ? : ) in C++ be compile time?

Can the ternary (conditional) operator be used as an analogous to constexpr if(), introduced in C++17?
I would like to add some conditionality to member variables initialization in a template. Would the following expression resolve at compile time or runtime? If so, is there any other operator that resolves at compile time such that template specialisation can be avoided?
template<int a>
struct hello {
constexpr static int n = (a != 0) ? 10 : 20;
}
It depends on what you mean by "analogous to constexpr if()". if constexpr requires that the condition is a constant expression. It also has certain privileges in template code to discard the branches not taken.
?: does not have that functionality.
However ?: can appear in constant expressions just fine, and it always could. It doesn't make an expression non-constant.
Yes, it absolutely can and in fact it could already be used in C++11 before the introduction of if constexpr, and even before C++11 in constant expressions, such as the one in your question.

`if constexpr` vs `if` in light of compiler optimization and code performance

Consider a function template func that is very performance critical. It can be instantiated with T=Type1 or some other type. Part of the function logic depends on T it is instantiated with.
One can either explicitly use a if constexpr (Code B) or use a vanilla if instead (Code A), while compiler probably optimizes the code.
However, I wonder, how the implementation without constexpr (Code A) is any different? Isn't the compiler capable of detecting which branch of if (in Code A) to use at compile time while instantiating? Can it still (for Code A) generate a less efficient code?
Code A. Without if constexpr:
template<class T>
void func(T argument)
{
// some general type-independent logic
if (std::is_same<Type1,T>::value)
{
// do something
}
else
{
// do something else
}
// some general type-independent logic
}
Code B. With if constexpr:
template<class T>
void func(T argument)
{
// some general type-independent logic
if constexpr (std::is_same<Type1,T>::value)
{
// do something
}
else
{
// do something else
}
// some general type-independent logic
}
Both codes A & B compile, as do something and do something else are well-formed for any T.
There are some similar-sounding questions:
Why is constexpr if needed? – this one answers when constexpr is required.
Difference between if and constexpr if – just lists the differences
The aforementioned questions do not answer if Code B is preferable to Code A for some reason (when both branches are well-formed anyway).
The only advantage I see would be to tell the programmer explicitly that this if is compile-time; however, I would say the conditional expression is self-explanatory.
if constexpr is not intended about optimization. Compilers are very good at optimizing away a branch that is if (true) or if (false) (since we're talking about constant expressions, that is what it boils down to). Here is a godbolt demo of the example in OP - you'll note that both gcc and clang, even on -O0, do not emit a branch for a simple if.
if constexpr is all about ensuring that only one branch of the if is instantiated. This is hugely important and valuable for writing templates - because now we can actually write conditionally compiling code within the body of the same function instead of writing multiple artificial functions just to avoid instantiation.
That said, if you have a condition that is a known constant expression - just always use if constexpr, whether or not you need the instantiation benefit. There is no downside to such a decision. It makes it clearer to readers that indeed this condition is constant (since otherwise it wouldn't even compile). It will also force the evaluation of the expression as a constant (a slight variant leads gcc to emit a branch at -O0, thought not at -O1), which with the coming addition of is_constant_evaluated() may become more important in the long run (possibly even negating my opening paragraph).
The only advantage I see would be to tell the programmer explicitly that this if is compile-time; however, I would say the conditional expression is self-explanatory.
To address this specifically, yes, std::is_same<X, Y>::value is "self-explanatory" that it is a constant expression... because we happen to be familiar with std::is_same. But it's less obvious whether foo<X>::value is a constant expression or whether foo<X>() + bar<Y>() is a constant expression or anything more arbitrarily complicated than that.
It's seeing if constexpr that makes the fact that it's compile-time self-explanatory, not the content of the condition itself.
Adding an example to #Barry 's explanation: The use is primarily for writing templates. Consider the following:
template <class T>
auto get_value()
{
if constexpr (std::is_same_v<T, int>) {
return 1
} else {
return 2.0;
}
}
You can note that, if the template parameter is int, the return value is determined to be int, while it is float when the template parameter is not int. You will see that this does not work with non-constexpr if statements, because at instantiation, all returns of a function must have a common type, which the former does not have. The only other way of achieving this is to use c++20 contraints, or std::enable_if to overload the function based on the template parameter.

Is "if constexpr" useful outside of templates?

I'm trying to understand if constexpr fully.
I understand, that if if constexpr(expr) used in a template, and expr is dependent on a template parameter, then during instantiation, only one of the then/else branches will be instantiated, the other will be discarded.
I've got two questions:
Is it true, that if expr is not dependent on a template parameter, then no branches of if constexpr(expr) will be discarded? If yes, where does the standard say so? I don't see where the standard has the exception that discard happens only when expr is dependent.
Is if constexpr useful outside of templates? If yes, what are the use cases of this? Can you give some examples to understand its usefulness?
Is it true, that if expr is not dependent on a template parameter, then no branches of if constexpr(expr) will be discarded? If yes, where does the standard say so? […]
Yes, that is true. You're looking for [stmt.if]/2. Specifically this part:
[…] During the instantiation of an enclosing templated entity, if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated. […]
The best example I could find for a case where you would end up being value-dependent after instantiation is the one given by cppreference.com:
template<class T> void g() {
auto lm = [](auto p) {
if constexpr (sizeof(T) == 1 && sizeof p == 1) {
// this condition remains value-dependent after instantiation of g<T>
}
};
}
Is if constexpr useful outside of templates? If yes, can you give some examples to understand its usefulness?
While all branches will be instantiated when the if constexpr does not appear inside of a template, [basic.def.odr]/10 still applies:
Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement; […]
emphasis mine. That effectively means that an odr-use of an entity in a discarded statement doesn't count. For example:
void blub();
constexpr bool use_blub = false;
void f()
{
if constexpr (use_blub)
{
blub();
}
}
The call to blub() will not require that your program have a definition of blub() if the condition is false. Using a normal if, the program would still be required to provide a definition of blub() somewhere, even if it is never used. So you could, e.g., use if constexpr to toggle between calling some library function and calling some fallback implementation depending on whether the library is available (and being linked to). Apart from that, hypothetically, a compiler might not warn about unreachable code if it is unreachable due to an if constexpr like it potentially would with a normal if. I couldn't come up with an example of this using any actual compiler, however…

Detect whether a type is an zero-element array?

Consider the following function :
template <typename Type>
void f(const Type& x)
I would like to do something special (without specialization) in it whether the passed type is an empty std::tuple or an empty std::array. For a tuple of nu elements, I can use std::is_same<Type, std::tuple<>>::value but what trick can I use to detect an zero-element array ?
(I am searching for a solution that do not require the creation of another function or class, ...)
You can use std::tuple_size, as it will also work for std::array! See here. Simply use:
std::tuple_size<Type>::value == 0
to check if Type is an empty std::tuple<> or an empty std::array<T,0>.
With the above, the question remains what happens if Type is neither a std::tuple not a std::array. The general approach I see is this:
constexpr bool IsNotTupleOrArray =
!std::is_class<Type>::value ||
std::is_same<Type,ExcludeThisClass>::value ||
sizeof(Type)>1 || // non-portable, works for GCC 4.8+
...;
std::conditional< IsNotTupleOrArray,
std::false_type,
std::tuple_size<Type> >::type::value;
which basically means that you have to explicitly exclude other types. For example is_class<Type> excludes all fundamental types like int, pointers, etc.
If you don't want to do it without specialization, why not doing it with overload? ;) Specializing a function template is sometimes (no, often) a bad idea. With some SFINAE trick it wouldn't be difficult to create an overload that is selected only when those two conditions apply.
However, I already hear you shouting that it's not what you wanted to do: what you wanted is some kind of if inside of f() that would be executed in case the condition is true, and a corresponding else branch that would be executed when the condition is false.
However, notice that this would not be a static if, but a regular run-time if: in other words, the compiler would know at compile-time with 100% certainty that one of those two branches is never going to be executed (and it would probably issue an annoying warning about it), but it will have to parse both branches and prove them legal.
In practice, this means that you won't be able to put statements that depend on the particular type T (or on properties of T) in order to be compilable. For instance, in the code below compile_time_expression determines whether type T has a member function foo() or a member function bar():
T obj;
if (compile_time_expression)
{
obj.foo();
}
else
{
obj.bar();
}
However, the above won't compile if that particular T doesn't have both a member function foo() and a member function bar(): since what you have here is a run-time if, the compiler will have to parse both branches and make sure they're both compilable - and then possibly optimize away the one that is never going to be executed.
Unfortunately, C++ does not have any construct such as static if (yet?), so overloading + SFINAE is the right way to tackle this problem.