constexpr 如果和 static_assert

constexpr if and static_assert

P0292R1 constexpr if has been included,在 C++17 的轨道上。它似乎很有用(并且可以代替 SFINAE 的使用),但是关于 static_assert 的评论 格式错误,不需要诊断 在错误的分支中吓到我了:

Disarming static_assert declarations in the non-taken branch of a
constexpr if is not proposed.

void f() {
  if constexpr (false)
    static_assert(false);   // ill-formed
}

template<class T>
void g() {
  if constexpr (false)
    static_assert(false);   // ill-formed; no 
               // diagnostic required for template definition
}

我认为完全禁止在 constexpr if 中使用 static_assert(至少是 false / non-taken 分支,但实际上这意味着这样做既不安全也不有用)。

这是如何从标准文本中得出的?我发现提案措辞中没有提到 static_assert,C++14 constexpr 函数确实允许 static_assert(cppreference 中的详细信息:constexpr)。

是否隐藏在这个新句子中(6.4.1之后)? :

When a constexpr if statement appears in a templated entity, during an instantiation of the enclosing template or generic lambda, a discarded statement is not instantiated.

从那以后,我假设也禁止调用其他 constexpr(模板)函数,调用图中 某处 可能会调用 static_assert.

底线:

如果我的理解是正确的,那么这不是对 constexpr if 的安全性和实用性施加了相当严格的限制,因为我们必须(通过文档或代码检查)了解 [=] 的任何使用13=]?我的担心是不是错了?

更新:

此代码在没有警告的情况下编译 (clang head 3.9.0) 但据我所知 格式错误 ,不需要诊断。有效与否?

template< typename T>
constexpr void other_library_foo(){
    static_assert(std::is_same<T,int>::value);
}

template<class T>
void g() {
  if constexpr (false)
    other_library_foo<T>(); 
}

int main(){
    g<float>();
    g<int>();
}

编辑: 我将通过示例和对导致此问题的误解的更详细解释来保留此自我回答。 T.C 的简短回答。足够了。

current draft 中重新阅读提案和 static_assert 后,我得出结论,我的担心是错误的。首先,这里的重点应该放在template definition.

ill-formed; no diagnostic required for template definition

如果模板是 实例化的 ,任何 static_assert 都会按预期触发。这大概与我引用的声明很吻合:

... a discarded statement is not instantiated.

这对我来说有点模糊,但我得出结论,这意味着丢弃语句中出现的templates不会被实例化。其他代码 但是必须在句法上有效。因此,当实例化包含 static_assert 的模板时,丢弃的 if constexpr 子句中的 static_assert(F),[其中 F 为假,无论是字面上的还是一个 constexpr 值] 仍将是 'bite'。或者(不需要,由编译器决定)如果已知它始终为假,则已经在声明中。

示例:(live demo)

#include <type_traits>

template< typename T>
constexpr void some_library_foo(){
    static_assert(std::is_same<T,int>::value);
}

template< typename T>
constexpr void other_library_bar(){
    static_assert(std::is_same<T,float>::value);
}

template< typename T>
constexpr void buzz(){
    // This template is ill-formed, (invalid) no diagnostic required,
    // since there are no T which could make it valid. (As also mentioned
    // in the answer by T.C.).
    // That also means that neither of these are required to fire, but
    // clang does (and very likely all compilers for similar cases), at
    // least when buzz is instantiated.
    static_assert(! std::is_same<T,T>::value);
    static_assert(false); // does fire already at declaration
                          // with latest version of clang
}

template<class T, bool IntCase>
void g() {
  if constexpr (IntCase){
    some_library_foo<T>();

    // Both two static asserts will fire even though within if constexpr:
    static_assert(!IntCase) ;  // ill-formed diagnostic required if 
                              // IntCase is true
    static_assert(IntCase) ; // ill-formed diagnostic required if 
                              // IntCase is false

    // However, don't do this:
    static_assert(false) ; // ill-formed, no diagnostic required, 
                           // for the same reasons as with buzz().

  } else {
    other_library_bar<T>();
  }      
}

int main(){
    g<int,true>();
    g<float,false>();

    //g<int,false>(); // ill-formed, diagnostic required
    //g<float,true>(); // ill-formed, diagnostic required
}

static_assert 上的标准文本非常短。在标准语中,这是一种使程序 格式错误 具有诊断功能的方法(正如@immibis 也指出的那样):

7.6 ... If the value of the expression when so converted is true, the declaration has no effect. Otherwise, the program is ill-formed, and the resulting diagnostic message (1.4) shall include the text of the string-literal, if one is supplied ...

这是在谈论模板的公认规则 - 允许编译器诊断的相同规则 template<class> void f() { return 1; }[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 ([stmt.if]) within a template and the template is not instantiated, or
  • [...]

无法为包含 static_assert 的模板生成有效的特化,该模板的条件是非依赖的且计算结果为 false,因此该程序是格式错误的 NDR。

static_assert 具有可评估为至少一种类型的 true 的从属条件不受影响。

您的自答,也可能是 T.C 的自答。不太正确。

首先,“即使在 if constexpr 内,两个静态断言都会触发”这句话是不正确的。他们不会,因为 if constexpr 条件取决于模板参数。
您可以看到,如果您在示例代码中注释掉 static_assert(false) 语句和 buzz() 的定义: static_assert(!IntCase) 不会触发,它会编译。

此外,在丢弃的 constexpr ifAlwaysFalse<T>::value! std::is_same_v<T, T> (并且没有效果),即使没有 T 他们评估为 true.
我认为“无法生成有效的专业化”在标准中是错误的措辞(除非 cppreference 是错误的;然后 T.C. 是正确的)。它应该说“可以生成”,并进一步说明“可以”的含义。

这与此上下文中 AlwaysFalse<T>::value! std::is_same_v<T, T> 的问题有关(这是对此答案的评论)。
我会争辩说它们是,因为它是“可以”而不是“可以”,并且在实例化时对于所有类型都是错误的。
std::is_same 和这里的非标准包装器之间的关键区别在于后者在理论上可以是专门的(感谢 cigien,指出这一点并提供 link)。

NDR 是否格式错误的问题也关键取决于模板是否实例化,只是为了完全清楚。

C++20 现在使 if constexprelse 分支中的 static_assert 更短,因为它允许模板 lambda 参数。因此,为了避免 ill-formed 的情况,我们现在可以定义一个带有 bool 模板 non-type 参数的 lambda,我们用它来触发 static_assert。我们立即使用 () 调用 lambda,但由于如果未采用其 else 分支,则不会实例化 lambda,因此除非实际采用 else,否则不会触发断言:

template<typename T>
void g()
{
    if constexpr (case_1)
        // ...
    else if constexpr (case_2)
        // ...
    else
        []<bool flag = false>()
            {static_assert(flag, "no match");}();
}

这是我遇到过的解决此问题的最简洁方法(at least in current compilers) is to use !sizeof(T*) for the condition, detailed by Raymond Chen here。这有点奇怪,从技术上讲并不能解决格式错误的问题,但至少它很短而且不需要包含或定义任何内容。对其进行解释的小评论可能会对读者有所帮助:

template<class T>
void g() {
  if constexpr (can_use_it_v<T>) {
    // do stuff
  } else {
    // can't use 'false' -- expression has to depend on a template parameter
    static_assert(!sizeof(T*), "T is not supported");
  }
}

使用 T* 的要点是仍然为不完整的类型提供正确的错误。

我也遇到了这个 discussion in the old isocpp mailing list,这可能会增加这个讨论。有人提出了一个有趣的观点,即做这种有条件的 static_assert 并不总是最好的主意,因为它不能用于 SFINAE-away 重载,这有时是相关的。