失败的 static_assert 如何在 if constexpr (false) 块中工作?

发布于 2025-01-11 13:09:15 字数 1802 浏览 0 评论 0原文

P0292R1 constexpr 如果包含,有望用于 C++17。它看起来很有用(并且可以取代 SFINAE 的使用),但是关于 false 分支中 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之后)? :

当 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>();
}

P0292R1 constexpr if has been included, on track for C++17. It seems useful (and can replace use of SFINAE), but a comment regarding static_assert being ill-formed, no diagnostic required in the false branch scares me:

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
}

I take it that it's completely forbidden to use static_assert inside constexpr if (at least the false / non-taken branch, but that in practice means it's not a safe or useful thing to do).

How does this come about from the standard text? I find no mentioning of static_assert in the proposal wording, and C++14 constexpr functions do allow static_assert (details at cppreference: constexpr).

Is it hiding in this new sentence (after 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.

From there on, I assume that it is also forbidden, no diagnostic required, to call other constexpr (template) functions which somewhere down the call graph may call static_assert.

Bottom line:

If my understanding is correct, doesn't that put a quite hard limit on the safety and usefulness of constexpr if as we would have to know (from documentation or code inspection) about any use of static_assert? Are my worries misplaced?

Update:

This code compiles without warning (clang head 3.9.0) but is to my understanding ill-formed, no diagnostic required. Valid or not?

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>();
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

半窗疏影 2025-01-18 13:09:15

这是关于模板的一个完善的规则 - 允许编译器诊断 template的相同规则。无效 f() { 返回 1; }[temp.res]/8,新更改以粗体显示:

程序格式错误,无需诊断,如果:

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

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

具有至少一种类型可评估为 true 的依赖条件的 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.

煮茶煮酒煮时光 2025-01-18 13:09:15

C++20 现在使 if constexprelse 分支中的 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");}();
}

C++20 makes static_assert in the else branch of if constexpr much shorter now, because it allows template lambda parameters. So to avoid the ill-formed case, we can now define a lambda with a bool template non-type parameter that we use to trigger the static_assert. We immediately invoke the lambda with (), but since the lambda won't be instantiated if its else branch is not taken, the assertion will not trigger unless that else is actually taken:

template<typename T>
void g()
{
    if constexpr (case_1)
        // ...
    else if constexpr (case_2)
        // ...
    else
        []<bool flag = false>()
            {static_assert(flag, "no match");}();
}
伤痕我心 2025-01-18 13:09:15

编辑:我将保留这个自我回答,其中包含示例以及对导致此问题的误解的更详细解释。 TC 的简短回答已经足够严格了。

重读提案和 当前草案,我的结论是我的担忧是错误的。首先,这里的重点应该是模板定义

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

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

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

这对我来说有点模糊,但我得出的结论是,这意味着出现在被丢弃语句中的模板将不会被实例化。其他代码
但在语法上必须是有效的。因此,当模板包含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-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)应包括文本
字符串文字,如果提供的话...

Edit: I'm keeping this self-answer with examples and more detailed explanations of the misunderstandings that lead to this questions. The short answer by T.C. is strictly enough.

After rereading the proposal and on static_assert in the current draft, and I conclude that my worries were misguided. First of all, the emphasis here should be on template definition.

ill-formed; no diagnostic required for template definition

If a template is instantiated, any static_assert fire as expected. This presumably plays well with the statement I quoted:

... a discarded statement is not instantiated.

This is a bit vague to me, but I conclude that it means that templates occurring in the discarded statement will not be instantiated. Other code
however must be syntactically valid. A static_assert(F), [where F is false, either literally or a constexpr value] inside a discarded if constexpr clause will thus still 'bite' when the template containing the static_assert is instantiated. Or (not required, at the mercy of the compiler) already at declaration if it's known to always be false.

Examples: (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
}

The standard text on static_assert is remarkably short. In standardese, it's a way to make the program ill-formed with diagnostic (as @immibis also pointed out):

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 ...

软糯酥胸 2025-01-18 13:09:15

对于旧的编译器,这仍然是错误的,我遇到的最简洁的方法来解决这个问题(至少在当前的编译器中)是使用 !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-away 重载,这有时是相关的。

For older compilers where this is still ill-formed, the most concise way I've come across to work-around this (at least in current compilers) is to use !sizeof(T*) for the condition, detailed by Raymond Chen here. It's a little weird, and doesn't technically get around the ill-formed problem, but at least it's short and doesn't require including or defining anything. A small comment explaining it may assist readers:

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");
  }
}

The point of using T* is to still give the proper error for incomplete types.

I also came across this discussion in the old isocpp mailing list which may add to this discussion. Someone there brings up the interesting point that doing this kind of conditional static_assert is not always the best idea, since it cannot be used to SFINAE-away overloads, which is sometimes relevant.

小忆控 2025-01-18 13:09:15

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

它被应用于 clang 和 所有 C++ 模式href="https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=9944ca17c0766623bce260684edc614def7ea761" rel="noreferrer">GCC 13

This has been found to be a defect, CWG 2518. Static asserts now are ignored in template declarations, so now are delayed until instantiation. Failing static asserts are no longer ill-formed no diagnostic required during template resolution.

It is being applied to all C++ modes in clang and GCC 13.

好听的两个字的网名 2025-01-18 13:09:15

逗号表达式可以使 static_assert 条件依赖于模板参数:

  #include <type_traits>

  template<typename T>
  constexpr int func(T x)
  {
    if constexpr(std::is_same_v<T, int>){
      return x;
    } else {
      static_assert((sizeof(T), false), "Bad template argument");
      return 0;
    }
   }

或者您可以创建一个始终为 false 的依赖表达式:

      static_assert(sizeof(T) == 0, "Bad template argument");

A comma expression can make the static_assert condition dependent on template arguments:

  #include <type_traits>

  template<typename T>
  constexpr int func(T x)
  {
    if constexpr(std::is_same_v<T, int>){
      return x;
    } else {
      static_assert((sizeof(T), false), "Bad template argument");
      return 0;
    }
   }

Or you can make a dependent expression which is always false:

      static_assert(sizeof(T) == 0, "Bad template argument");
⒈起吃苦の倖褔 2025-01-18 13:09:15

您的自我回答以及 TC 的回答可能不太正确。

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

此外,诸如 AlwaysFalse::value! std::is_same_v

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

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

Your self-answer and possibly the one by T.C. are not quite correct.

First of all, the sentence "Both two static asserts will fire even though within if constexpr" is not correct. They won't because the if constexpr condition depends on a template parameter.
You can see that if you comment out the static_assert(false) statements and the definition of buzz() in your example code: static_assert(!IntCase) won't fire and it will compile.

Furthermore, things like AlwaysFalse<T>::value or ! std::is_same_v<T, T> are allowed (and have no effect) inside a discarded constexpr if, even if there's no T for which they evaluate to true.
I think that "no valid specialization can be generated" is bad wording in the standard (unless cppreference is wrong; then T.C. would be right). It should say "could be generated", with further clarification of what is meant by "could".

This is related to the question whether AlwaysFalse<T>::value and ! std::is_same_v<T, T> are equivalent in this context (which is what the comments to this answer are about).
I would argue that they are, since it's "can" and not "could" and both are false for all types at the point of their instantiation.
The crucial difference between std::is_same and the non-standard wrapper here is that the latter could theoretically be specialized (thanks, cigien, for pointing this out and providing the link).

The question whether ill-formed NDR or not also crucially depends on whether the template is instantiated or not, just to make that entirely clear.

九命猫 2025-01-18 13:09:15

我的解决方案是:

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.");

My solution is:

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.");
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文