Constexpr If and Static_Assert

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 a constexpr 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_asserts 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



Leave a reply



Submit