constexpr if and static_assert
This is talking about a well-established rule for templates - the same rule that allows compilers to diagnose template<class> void f() { return 1; }
. [temp.res]/8 with the new change bolded:
The program is ill-formed, no diagnostic required, if:
- no valid specialization can be generated for a template or a substatement
of aconstexpr if
statement ([stmt.if]) within a
template and the template is not instantiated, or- [...]
No valid specialization can be generated for a template containing static_assert
whose condition is nondependent and evaluates to false
, so the program is ill-formed NDR.
static_assert
s with a dependent condition that can evaluate to true
for at least one type are not affected.
Is this use of static_assert inside if constexpr well-formed?
Both of your attempts (with the function and with the struct) are well-formed as is.
The other answer mentions [temp.res]/8
, but I disagree with how it was interpreted.
The validity of a template may be checked prior to any instantiation. ... The program is ill-formed, no diagnostic required, if:
— no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or ...
Both the function and struct you wrote can be specialized to be true
. I believe the mere possibility of specialization is enough, you don't actually need to add a dummy true
specialization to make the code well-formed.
According to my understanding (and according to common sense, I hope), the point of this part of the standard is to allow compilers to check validity of templates and if constexpr
branches early (when they are seen for the first time), and reject the ones that can't possibly be instantiated.
Every branch in your template can potentially be instantiated, because bool_value()
can be specialized later. I doubt a sane compiler is going to reject your code due to bool_value()
not being specialized yet.
c++ static_assert fails on both branches of an 'if constexpr statement'
Constexpr if is supposed to work with template:
Outside a template, a discarded statement is fully checked.
if constexpr
is not a substitute for the #if preprocessing directive:void f() {
if constexpr(false) {
int i = 0;
int *p = i; // Error even though in discarded statement
}
}
And
Note: the discarded statement can't be ill-formed for every possible specialization:
template <typename T>
void f() {
if constexpr (std::is_arithmetic_v<T>)
// ...
else
static_assert(false, "Must be arithmetic"); // ill-formed: invalid for every T
}
You can wrap the code into a function template like:
template<class T> struct dependent_false : std::false_type {};
template <typename T>
void foo() {
if constexpr (is_pair_type<T>::value)
{
std::cout << "HERE1" << "\n";
static_assert(dependent_false<T>::value, "HERE1");
}
else
{
std::cout << "HERE2" << "\n";
static_assert(dependent_false<T>::value, "HERE2");
}
}
Then
using T = std::map<int, float>;
foo<T>(); // static_assert fails only on the else part
LIVE
if constexpr with static_assert in lambda, which compiler is correct?
From [stmt.if]/2 (emphasis mine)
If the if statement is of the form if constexpr, the value of the condition shall be a contextually converted constant expression of type bool; this form is called a constexpr if statement. If the value of the converted condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. During the instantiation of an enclosing templated entity ([temp.pre]), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.
Reading that one would think the static assert would be dropped, but this is not the case.
The static assert is triggered in the first phase of the template because the compiler know it's always false.
From [temp.res]/8 (emphasis mine)
The validity of a template may be checked prior to any instantiation. [ Note: Knowing which names are type names allows the syntax of every template to be checked in this way. — end note ]
The program is ill-formed, no diagnostic required, if:
- (8.1) no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or
[...]
Yes indeed, your False<T>
depends on T
. The problem is that a generic lambda is itself a template, and False<T>
is not dependent on any template parameter of the lambda.
For a T
that False<T>
is false, the static assert will always be false, no matter which template argument is sent to the lambda.
The compiler can see that for any instantiation of the template operator()
, the static assert will always trigger for the current T. Hence the compiler error.
A solution for this would be to depend on x
:
template<typename T>
void foo() {
auto f = [](auto x) {
if constexpr(x < 0) {
static_assert(False<decltype(x)>, "AAA");
}
};
f(std::integral_constant<int, 1>{});
}
Live example
How to assert that a constexpr if else clause never happen?
You have to make the discarded statement dependent of the template parameters
template <class...> constexpr std::false_type always_false{};
if constexpr(condition1){
...
} else if constexpr (condition2) {
....
} else if constexpr (condition3) {
....
} else {
static_assert(always_false<T>);
}
This is so because
[temp.res]/8 - The program is ill-formed, no diagnostic required, if
no valid specialization can be generated for a template or a substatement of a
constexpr if
statement within a template and the template is not instantiated, or ...
Stop compilation if `if constexpr` does not match
The soltuion is to use static_assert
.
But we can't simply do static_assert(false, "whatever");
in the else
branch, because since the condition doesn't depend on the template parameter, assertion might fire early (when the compiler first sees your function body, even if the else
branch is never actually taken).
The condition of static_assert
has to somehow depend on T
, to delay the assertion check until your template is instantinated.
This is what I've been using:
template <auto A, typename...> auto value = A;
if constexpr (foo)
{
...
}
else if constexpr (bar)
{
...
}
else
{
static_assert(value<false, T>, "Invalid template parameter.");
}
Note that if you only have one if constexpr
(rather than an if
else if
chain), then none of that is needed.
Simply move the condition from if
to a static_assert
and remove the if
.
if constexpr and dependent false static_assert is ill formed?
no valid specialization can ever be generated for the relevant
if constexpr
substatements
Well no.
You could have a valid specialization of that branch if you specialized dependent_false
first:
template <> struct dependent_false<int> : std::true_type {};
///
foo<int>(); // <-- A valid specialization for the `else` branch is possible here
The fact that it's possible to create such a specialization (even if you don't have one) makes static_assert(dependent_false<T>::value);
well-formed.
On the other hand, static_assert(!std::is_same_v<T, T>);
is ill-formed NDR, because specializing templates from std::
is not allowed.
Related Topics
Functions With Const Arguments and Overloading
Dividing Two Integers to Produce a Float Result
How to Get Iostream to Perform Better
Append an Int to a Std::String
Behavior of Post Increment in Cout
Order of Member Constructor and Destructor Calls
Comparing Iterators from Different Containers
C++: What Regex Library Should I Use
In C++ How to Go to a Specific Line in a Text File
How to Use a Member Variable as a Default Argument in C++
What Are the Pointer-To-Member Operators -≫* and .* in C++
What Makes More Sense - Char* String or Char *String
What Are Some Reasons a Release Build Would Run Differently Than a Debug Build
Can Placement New For Arrays Be Used in a Portable Way
Different Floating Point Result With Optimization Enabled - Compiler Bug
C++ How to Determine Whether a Pointer Points to a Valid Object