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…
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.
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.
If a function template returns decltype(auto) (or another type specifier using auto) but the return statement would be ill-formed, does SFINAE result? Is the return statement considered to be the immediate context of the function signature?
Nothing in the N3690 draft seems to require this. By default, I guess SFINAE does not apply.
This seems unfortunate because you can write a function to forward to another function, but you cannot make its existence conditional on the delegate as when writing longhand. Furthermore, checking the existence of a peer nonstatic member function cannot be done without decltype(auto) because this cannot be used in a function signature. However this indicates a fundamental problem, as decltype(auto) provides a path to considering the class type as complete within a member signature, where it's not.
Has a proposal been written, or has the problem been formally analyzed anywhere?
The ability to treat the class type as complete within a member signature may have other implications… but that's just fodder for another question.
but the return statement would be ill-formed, does SFINAE result?
The proposal-n3638 says,
SFINAE
Since the return type is deduced by instantiating the template, if the instantiation is ill-formed, this causes an error rather than a substitution failure. This allows an auto function to return a lambda, which is not possible using the decltype(returned expression) pattern.
Hope that is what you're looking for.
Following up on Nawaz's link, the remaining questions are answered by N3690 §7.1.6.4/11:
If the type of an entity with an undeduced placeholder type is needed to determine the type of an expression, the program is ill-formed.
This means that even if SFINAE worked with return type deduction, it couldn't be used to query one function declaration from another. The signature is essentially invalid until the return statement is processed, which occurs at the closing brace of the class {} definition, and after the definitions of preceding members have been processed.
In a sense, all member decltype(auto) functions are incomplete with respect to preceding functions in the same class:
struct s {
void f() { a(); } // error: use of ‘auto s::a()’ before deduction of ‘auto’
auto a() { return 3; }
};
This is GCC's complaint; it goes away if the member declarations are reversed. This is because the function definitions are processed in order of declaration, when the } from the class definition is reached. If the statement a(); is processed before the return 3;, then the program is ill-formed.
With the definition shown below, I can call qget<0>() or qget<1>() using G++ (4.7.2), but qget<2> or "higher" will fail with a no matching function error. Clang++ (3.2), meanwhile, fails with any of them. I've used lazy enable_if as a last resort; though I don't think I should need it. I know the code looks a bit odd, but can anyone see the source of the error? (Boost provides the enable_if classes.)
template <typename T> struct Tid { typedef T type; };
template <unsigned I>
typename enable_if_c<(I==0),double>::type
qget()
{ return 0.0; }
template <unsigned I>
typename lazy_enable_if_c<(I!=0), Tid<decltype(qget<I-1>())>>::type
qget()
{ return qget<I-1>(); }
When you declare a function or function template as e.g. ret foo(A, B, C); or indifferently as auto foo(A, B, C) -> ret;, then the foo that refers to the entity just declared is not in scope until after the so-called declarator. In your particular case, the return type (be it a late return type or not) is always part of the declarator.
This means that in your last declaration the name qget in the return type may refer to the previous declaration (the case for I==0) but may never refer to the current declaration. This is why qget<0> and qget<1> are found, but qget<2> is not: when attempting to form the return type of the latter, qget<1> is not found because the first declaration is SFINAE'd out, as intended and the second declaration is the current declaration and not in scope. The error results in SFINAE.
My usual solution when this happens (which I have to say is not that often) is to use a struct (as an implementation detail) because all member functions (and member function templates) are declared inside the definition of a class, from the opening brace on.
That being said, you'll still hit a snag because even though you are using lazy_enable_if_c you're still eagerly computing the type of qget<I - 1>() (as a parameter to lazy_enable_if_c) even when I is 0. Lazily evaluating the identity of said type won't save you.
Unfortunately I can't seem to get an example running using GCC 4.7.2, which insists in not terminating the recursion even when fixing the condition to I > 0 and using a lazy result (although normally I'd use 4.8), so I can't promise you my solution can be made to work.