I am trying to use if constexpr
in the following way:
template<template <typename First, typename Second> class Trait,
typename First, typename Second, typename... Rest>
constexpr bool binaryTraitAre_impl()
{
if constexpr (sizeof... (Rest) == 0)
{
return Trait<First, Second>{}();
}
return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
}
Example use case:
static_assert(binaryTraitAre_impl<std::is_convertible,
int, int&,
int*, void*>());
But this fails to compile
error: no matching function for call to 'binaryTraitAre_impl'
return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prog.cc: In instantiation of 'constexpr bool binaryTraitAre_impl() [with Trait = std::is_convertible; First = int*; Second = void*; Rest = {}]':
prog.cc:9:80: required from 'constexpr bool binaryTraitAre_impl() [with Trait = std::is_convertible; First = int; Second = int&; Rest = {int*, void*}]'
prog.cc:15:83: required from here
prog.cc:9:80: error: no matching function for call to 'binaryTraitAre_impl<template<class _From, class _To> struct std::is_convertible>()'
9 | return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
prog.cc:3:17: note: candidate: 'template<template<class First, class Second> class Trait, class First, class Second, class ... Rest> constexpr bool binaryTraitAre_impl()'
3 | constexpr bool binaryTraitAre_impl()
| ^~~~~~~~~~~~~~~~~~~
prog.cc:3:17: note: template argument deduction/substitution failed:
prog.cc:9:80: note: couldn't deduce template parameter 'First'
9 | return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
But I found the error goes away once I add else
:
template<template <typename First, typename Second> class Trait,
typename First, typename Second, typename... Rest>
constexpr bool binaryTraitAre_impl()
{
if constexpr (sizeof... (Rest) == 0)
{
return Trait<First, Second>{}();
}
else
{
return Trait<First, Second>{}() and binaryTraitAre_impl<Trait, Rest...>();
}
}
live demo
What happened? Why can the compiler not infer the else
in this case?
Constexpr ifIf the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.
Yes. I believe putting such const ness is always a good practice wherever you can. For example in your class if a given method is not modifying any member then you always tend to put a const keyword in the end.
This is a big one! The static-if for C++! The feature allows you to discard branches of an if statement at compile-time based on a constant expression condition.
Short answer: static_assert(false) should never appear in a constexpr if expression, regardless of whether it's in a template function or whether it's in the discarded branch.
As such, you can clearly see that the second constexpr is needed. 1: "block" is not the correct terminology. if is a statement (with optional else part). A block is { /*...*/ }. Thanks for contributing an answer to Stack Overflow!
Do we need to put constexpr after every if statement in if-else block in these kind of situations? Yes. The else-if block 1 is a lie :), there are only if blocks 1 and else blocks 1.
constexpr functions can easily be converted into regular functions as requirements change. constexpr functions compile much quicker than the equivalent template-based solutions, which scale linearly with the depth of the template-recursion. As an example, take a look at these two compile-time implementations of the Fibonacci sequence:
Before diving into if-constexpr, it might be useful to have a quick recap of constexpr. Introduced in C++ 11, constexpr is a keyword that marks an expression or function as having a compile-time constant result. OK, so you might be wondering what the purpose of constexpr is.
if constexpr
when the clause is true doesn't eliminate code outside of the corresponding else
block.
You could extend C++ to do that, but it quickly becomes a pain. Only the most trivial cases are obvious, and specifying what the trivial cases are is a pain. I mean do you cover:
if constexpr( blah ){
if (true) return 7;
}
? How about
if constexpr( blah ){
if (blah) return 7;
else exit(-1);
}
? Or
if constexpr( blah ){
if (blah) return 7;
else return false;
}
or
if constexpr( blah ){
if (blah) goto foo;
return false;
foo: return true;
}
or how about:
if constexpr( blah ){
std::size_t count = 0;
while (foo != 1 && (++count < (std::size_t)-1))
switch (foo%2) {
case 1: foo = 3*foo+1;
case 0: foo = foo/2;
}
}
if (count < (std::size_t)-1) return true;
}
? I can come up with a near continuum of cases that are slightly more or less "obvious" in their never-return. And the payoff? Not having an else
. Lots of problems, little benefit.
Compilers have ad-hoc rules to detect unreachable code and the like. These don't have to be as formally specified as the standard, and they can differ from one compiler to another.
The standard, meanwhile, has to be the same for every compiler. And the rules for what is and isn't eliminated have to be identical.
The standard applies a simple rule; the if
and else
blocks are the only candidates for eliminatation.
So the standard doesn't do that. If you want code to be eliminated, put it in a if constexpr
or else
block of the if constexpr
. Language development resources are better spent on things that have better yield and are less painful.
This is the excerpt from cppreference on constexpr if
:
Constexpr If The statement that begins with if constexpr is known as the constexpr if statement.
In a constexpr if statement, the value of condition must be a contextually converted constant expression of type bool. If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.
It is clear that only one of the two branches is discarded. In your case, the culprit code cannot be discarded because it's outside the else
clause.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With