如果在 lambda 中使用 static_assert 的 constexpr,哪个编译器是正确的?

if constexpr with static_assert in lambda, which compiler is correct?

当我们想在 if constexpr 中使用 static_assert 时,我们必须使条件依赖于某些模板参数。有趣的是,当代码被包裹在 lambda 中时,gcc 和 clang 不同意。

以下代码使用 gcc 编译,但 clang 触发断言,即使 if constexpr 不可能为真。

#include <utility>

template<typename T> constexpr std::false_type False;

template<typename T>
void foo() {

    auto f = [](auto x) {
        constexpr int val = decltype(x)::value;
        if constexpr(val < 0) {
            static_assert(False<T>, "AAA");
        }
    };

    f(std::integral_constant<int, 1>{});
}

int main() {
    foo<int>();
}

Live example here.

False<T>替换为False<decltype(x)>即可轻松修复。

所以问题是:哪个编译器是正确的?我假设 gcc 是正确的,因为 static_assert 中的条件取决于 T,但我不确定。

这里通常的规则是[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

实例化 foo<T> 后,您拥有的 static_assert 将不再依赖。它变为 static_assert(false) - 对于通用 lambda f 的调用运算符的所有可能实例化。这是错误的,不需要诊断。 Clang 诊断,gcc 没有。都是正确的。

注意这里的static_assert丢弃并不重要。

It can easily be fixed by substituting False<T> by False<decltype(x)>.

这使得 static_assert 依赖于通用 lambda,现在我们进入了一个假设可能存在有效特化的状态,因此我们不再是病式的,ndr。

来自 [stmt.if]/2(强调我的)

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.

读到有人会认为静态断言会被删除,但事实并非如此。

静态断言在模板的第一阶段触发,因为编译器知道它始终为假。

来自 [temp.res]/8(强调我的)

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

[...]

确实如此,您的 False<T> 取决于 T。问题是通用 lambda 本身就是一个模板,False<T> 不依赖于 lambda 的任何模板参数。

对于 False<T> 为假的 T,静态断言将始终为假,无论将哪个模板参数发送到 lambda。

编译器可以看到,对于模板的任何实例化 operator(),静态断言将始终针对当前 T 触发。因此出现编译器错误。

一个解决方案是依赖于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