constexpr if和static_assert

Joh*_*erg 31 c++ templates static-assert constexpr c++17

P0292R1 constexpr如果包含在内,正在进行 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
}
Run Code Online (Sandbox Code Playgroud)

我认为它是完全禁止static_assert在constexpr内部使用if(至少是假/非分支,但实际上这意味着它不是一个安全或有用的事情).

这是如何从标准文本中产生的?我发现static_assert在提案中没有提到措辞,并且C++ 14 constexpr函数允许static_assert(cppreference的详细信息:constexpr).

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

当constexpr if语句出现在模板化实体中时,在封闭模板或通用lambda的实例化期间,不会实例化丢弃的语句.

从那以后,我认为它也是禁止的,不需要诊断,调用调用图中某个地方可能调用的其他constexpr(模板)函数static_assert.

底线:

如果我的理解是正确的,那么对于constexpr if我们必须知道(从文档或代码检查)有关任何使用的安全性和实用性,这是不是一个非常严格的限制static_assert?我的担忧是否错位?

更新:

这段代码在没有警告的情况下编译(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>();
}
Run Code Online (Sandbox Code Playgroud)

T.C*_*.C. 24

这是关于模板的完善规则 - 允许编译器诊断的相同规则template<class> void f() { return 1; }.[temp.res]/8,新变化加粗:

如果出现以下情况,该计划格式错误,无需诊断:

  • 无法为模板中的模板或子constexpr if语句([stmt.if])生成有效的专门化,并且模板未实例化,或者
  • [...]

对于包含static_assert其条件不依赖且计算结果的模板,不能生成有效的特化false,因此程序是格式错误的NDR.

static_assert具有可以评估true至少一种类型的依赖条件的s 不受影响.


Nik*_* C. 15

C++20现在static_assertelse分支if constexpr变得更短了,因为它允许模板 lambda 参数。因此,为了避免格式错误的情况,我们现在可以定义一个带有bool模板非类型参数的 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");}();
}
Run Code Online (Sandbox Code Playgroud)

  • 在 C++17 中,我们可以使条件依赖于模板参数,例如 `static_assert(!sizeof(T), "no match");`。[现场观看](https://coliru.stacked-crooked.com/a/c44a266099961a6c) (17认同)
  • [P2593 委员会文件](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2593r0.html) 中引用了这个答案,其中将其描述为“可怕且错误”。该论文已被 C++23 接受,并将允许“static_assert(false)”。 (8认同)
  • 在 C++17 中,可以在外部定义模板函数: `template&lt;bool flag = false&gt; void static_no_match() { static_assert(flag, "no match"); }`,然后在 `else` 分支中使用 `static_no_match()`。 (5认同)
  • “else”分支用这个 lambda 看起来很难看。 (4认同)
  • 我完全不确定这是否规避了 IFNDR 规则:仍然存在子语句不存在有效特化的情况,尽管必须推断出 lambda 的 `operator()` 所选特化的有效性是否被计算在内为了这个目的。 (3认同)

Joh*_*erg 8

编辑:我将通过示例以及对导致此问题的误解的更详细解释保持自我解答。TC的简短回答就足够了。

重读的建议后并static_assert目前的草案,我得出结论,我的担心是错误的。首先,这里的重点应该放在模板定义上

格式错误 模板定义无需诊断

如果实例化了模板,则会static_assert发生任何预期的火灾。这大概可以与我引用的语句配合使用:

...废弃的语句未实例化。

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

示例:(现场演示

#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-formated, (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-formated diagnostic required if 
                              // IntCase is true
    static_assert(IntCase) ; // ill-formated diagnostic required if 
                              // IntCase is false

    // However, don't do this:
    static_assert(false) ; // ill-formated, 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-formated, diagnostic required
    //g<float,true>(); // ill-formated, diagnostic required
}
Run Code Online (Sandbox Code Playgroud)

标准文字static_assert非常简短。在standardese,它是一种方法,使程序形成不良与诊断(如@immibis还指出):

7.6 ...如果转换后的表达式的值为true,则声明无效。否则,程序格式不正确,如果提供了此信息,则诊断信息(1.4)应包含字符串文字的文本...

  • 几年过去了,您碰巧知道更好的解决方案吗?对于复杂的嵌套if-else,“ static_assert(IntCase)”非常不便。我真的很想在“ else”中调用“ static_assert(false)”。 (3认同)

gol*_*vok 6

我遇到的解决这个问题的最简洁的方法(至少在当前的编译器中)是使用!sizeof(T*)for 条件,Raymond Chen 在这里详细介绍了这一点。这有点奇怪,从技术上讲并不能解决格式错误的问题,但至少它很短并且不需要包含或定义任何内容。一条解释它的小评论可能会对读者有所帮助:

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");
  }
}
Run Code Online (Sandbox Code Playgroud)

使用的目的T*是仍然为不完整的类型给出正确的错误。

我还在旧的 isocpp 邮件列表中遇到了这个讨论,这可能会增加这个讨论。有人提出了一个有趣的观点,即执行这种条件static_assert并不总是最好的主意,因为它不能用于 SFINAE-away 重载,这有时是相关的。

  • 如果 constexpr 的分支对于所有实例化都无效,那么它仍然是 IFNDR。 (4认同)
  • 这就是“不需要诊断”的意思——实现可能会也可能不会进行足够的分析来确定它总是无效的。“延迟”“static_assert”可能是在(今天的)真实编译器中可能发生的事情,但它根本没有任何正式意义。 (2认同)
  • 委员会[考虑](http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p1830r1.pdf)在此提供*惯用语*。他们还非正式地考虑限制问题的范围,因为从技术上讲,编译器也可以“任意错误编译”执行此操作的程序。 (2认同)