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.
Related
It seems that the rules for the compile-time keywords: constexpr, consteval and constinit are defined well enough for compilers to warn if you misapply the label.
It would make sense that (much like inline) the compiler can, in all places, apply rules to determine if, in fact, code could have one of the compile-time keywords applied and, be forced, per language specification, to compile as much as possible as if the compile-time keywords had been applied.
Or, at a minimum, if a compile-time keyword is applied to a function and the code would have qualified with had the correct compile-time keywords been applied. Compile that function, as if all the functions called had the correct compile-time keywords.
Here is a simple example:
#include <tuple>
consteval std::tuple<int, int> init1(int x, int y)
{
return {x,y};
}
std::tuple<int, int>& foo1()
{
static constinit std::tuple<int, int> x=init1(1,2);
return x;
}
std::tuple<int, int> init2(int x, int y)
{
return {x,y};
}
std::tuple<int, int>& foo2()
{
static std::tuple<int, int> x=init2(1,2);
return x;
}
static std::tuple<int, int> x3=init2(1,2);
std::tuple<int, int>& foo3()
{
return x3;
}
see it here: https://godbolt.org/z/KrzGfnEo7
Note that init1/foo1 are fully specified with compile-time keywords and there is no need for a guard variable, since it is initialized completely (not just 0 filled).
The question is about why the compiler doesn't do the same in the cases of init2/foo2 or x3/foo3? Or more precisely why the compiler is allowed NOT to initialize fully at compile time.
EDIT (as per comment) see (Why do we need to mark functions as constexpr?) constexpr would be required. I am concerned more with cases of consteval and constinit. Could the specification be modified to require trying to resolve code at compile-time and not failing because of the absence of constexpr? This would allow interface contracts to just use constexpr as per the related question.
Or, at a minimum, if a compile-time keyword is applied to a function and the code would have qualified with had the correct compile-time keywords been applied.
The basis of your question is the assumption that these keywords are just variations on a theme, that a function which could have some of them ought to have a specific one based on the properties within the function alone.
That's not reasonable.
For functions, any function which could be constexpr could also be consteval. The difference is that one can be called during constant expression evaluation, and the other must be called only during constant expression evaluation. This is not an intrinsic property of the function definition; it is about how the function is to be used.
The compiler should not override the decision of a programmer to be able to use a function at runtime just because the function definition just so happens to also be legit for consteval.
It should also be noted that there is the requirements of a function definition for a consteval function are the same as for a constexpr function.
It is also not possible to know if a function definition ought to be constexpr or not. Remember: while a constexpr function explicitly forbids certain constructs from appearing in the definition, it also permits you to have certain constructs in the definition that aren't allowed to be evaluated during constant evaluation... so long as you never actually allow those code paths to be evaluated during constant evaluation. So there are many function definitions that just so happen to be valid for constexpr even if the programmer didn't intend for them to be evaluated at compile-time.
Lambdas get implicit constexpr definitions only because space in a lambda expression is at a premium.
For variables, implicit constexpr makes even less sense. A variable ought to be constexpr if you intend to use it in a constant expression later on. This requires it to have an initializer that is a constant expression. Implicit constexpr is a problem because you might start relying on the implicit constexpr rule, then change the initializer to no longer be a constant expression and get a strange error in the place where you used its constexpr properties.
It's better to make the user be explicit up-front than to misjudge what the user was trying to do and make a user think something is supposed to be valid when it wasn't intended.
And constinit is solely about ensuring that a user never accidentally uses a non-constant expression to initialize the variable. Making it implicit would make absolutely no sense, as changing the expression to no longer be a constant expression wouldn't cause compilation to fail. Compilers are smart enough to know when a variable is initialized with a constant expression and can decide how to handle that on its own.
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.
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.
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.
struct Test
{
static const int value = []() -> int { return 0; } ();
};
With gcc-4.6 I get something like, error: function needs to be constexpr. I have tried multiple combinations of putting constexpr at various places, but no luck.
Is constexpr supported for lambda functions as well (irrespective of return type specified or not) ? What is the correct syntax ?
Any work around possible ?
Update: As of C++17, lambdas are permitted in constant expressions.
Lambdas are currently (C++14) not allowed in constant expressions as per [expr.const]/(2.6), but they will once N4487 is accepted (which can be found in the working draft N4582):
This proposal suggests allowing lambda-expressions in constant
expressions, removing an existing restriction. The authors propose
that certain lambda-expressions and operations on certain closure
objects be allowed to appear within constant expressions. In doing so,
we also propose that a closure type be considered a literal type if
the type of each of its data-members is a literal type; and, that if
the constexpr specifier is omitted within the lambda-declarator, that
the generated function call operator be constexpr if it would satisfy
the requirements of a constexpr function (similar to the
constexpr inference that already occurs for implicitly defined
constructors and the assignment operator functions).
From the C++0x FDIS §7.1.5[dcl.constexpr]/1:
The constexpr specifier shall be applied only to the definition of a variable, the declaration of a function or function template, or the declaration of a static data member of a literal type.
A lambda expression is none of those things and thus may not be declared constexpr.
Prior to C++17 lambdas are not compatible with constexpr. They cannot be used inside constant expressions.
Starting with C++17 lambdas are constexpr where it makes sense. The proposal N4487 will be put into the C++17 standard. On his website Herb Sutter, chair of the ISO C++ committee, stated the following:
Lambdas are now allowed inside constexpr functions.
FFWD to year 2018 :)
auto my_const_expression_lambda = []()
constexpr -> bool
{
return true ;
}
Since c++17