constexpr if and static_assert

constexpr if and static_assert

本文关键字:assert static and if constexpr      更新时间:2023-10-16

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
}
我认为完全禁止在

constexpr 中使用 static_assert 如果(至少是虚假/未采取的分支,但实际上这意味着这不是一件安全或有用的事情)。

这是从标准文本中得出的?我发现提案措辞中没有提到static_assert,并且C++14 constexpr函数确实允许static_assert(cpp首选项中的详细信息: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>();
}

这是在谈论模板的既定规则 - 允许编译器诊断template<class> void f() { return 1; }的规则相同。[temp.res]/8,新更改以粗体显示:

程序格式不正确,无需诊断,如果:

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

无法为包含条件为非依赖且计算结果为 falsestatic_assert模板生成有效的专用化,因此程序的 NDR 格式不正确。

具有至少一种类型的评估结果为 true 的依赖条件的static_assert不受影响。

C++20 现在使if constexpr else分支中的static_assert时间大大缩短,因为它允许模板 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");}();
}

编辑: 我保留了这个自我答案,并举例和更详细的解释导致这个问题的误解。T.C.的简短回答已经足够严格了。

在重新阅读了该提案并static_assert了目前的草案之后,我得出结论,我的担忧是错误的。首先,这里的重点应该放在模板定义上。

格式不正确;模板定义不需要诊断

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

。丢弃的语句不会实例化。

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

示例:(现场演示)

#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 ...如果转换后的表达式值为 true,则声明无效。否则,程序格式不正确,并且 生成的诊断消息 (1.4) 应包括 字符串文字,如果提供了一个...

我遇到的解决此问题的最简洁的方法(至少在当前的编译器中)是使用 !sizeof(T*) 作为条件,由 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");
  }
}

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

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

已发现这是一个缺陷,CWG 2518。静态断言现在在模板声明中被忽略,因此现在延迟到实例化。失败的静态断言不再格式错误,在模板解析期间无需诊断。

它被应用于 clang 和 GCC 13 中的所有C++模式。

你的自我回答和T.C.的回答不太正确。

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

此外,像AlwaysFalse<T>::value! std::is_same_v<T, T>这样的东西在丢弃的constexpr if中是允许的(并且没有效果),即使没有T它们的评估结果为true.
我认为"无法生成有效的专业化"在标准中是糟糕的措辞(除非cpp偏好是错误的;那么T.C.是正确的)。它应该说"可以产生",并进一步澄清"可能"的含义。

这与AlwaysFalse<T>::value! std::is_same_v<T, T>在这种情况下是否等价的问题有关(这就是对本答案的评论).
我认为它们是,因为它是"可以"而不是"可以",并且在它们的实例化时对于所有类型的两者都是错误的.
std::is_same 和非标准包装器之间的关键区别在于,后者在理论上可能是专门的(感谢 cigien,指出这一点并提供链接)。

NDR 格式是否不正确的问题还关键取决于模板是否已实例化,只是为了完全清楚这一点。

我的解决方案是:

if constexpr (is_same_v<T,int>)
  // ...
else if constexpr (is_same_v<T,float>)
  // ...
else
  static_assert(std::is_same_v<T, void> && !std::is_same_v<T, void>, "Unsupported element type.");