简体   繁体   English

constexpr if 和 static_assert

[英]constexpr if and static_assert

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: P0292R1 constexpr if has been included , on track for 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
}

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).我认为完全禁止在 constexpr if 中使用static_assert (至少是 false / non-taken 分支,但实际上这意味着它不是安全或有用的事情)。

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 ).我发现提案措辞中没有提到static_assert ,C++14 constexpr 函数确实允许static_assert (cppreference 中的详细信息: constexpr )。

Is it hiding in this new sentence (after 6.4.1)?是否隐藏在这个新句子中(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 if 语句出现在模板化实体中时,在封闭模板或通用 lambda 的实例化期间,不会实例化丢弃的语句。

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 .从那以后,我假设它也被禁止,不需要诊断,调用其他 constexpr (模板)函数,这些函数在调用图的某个地方可能会调用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 ?如果我的理解是正确的,那么如果我们必须知道(从文档或代码检查中)关于static_assert的任何使用,那么这不会对constexpr if的安全性和实用性施加相当严格的限制吗? 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.这段代码在没有警告的情况下编译(clang head 3.9.0),但据我所知是格式错误,不需要诊断。 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>();
}

This is talking about a well-established rule for templates - the same rule that allows compilers to diagnose template<class> void f() { return 1; }这是关于模板的完善规则 - 允许编译器诊断template<class> void f() { return 1; }相同的规则template<class> void f() { return 1; } template<class> void f() { return 1; } . template<class> void f() { return 1; } . [temp.res]/8 with the new change bolded: [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不能为模板模板中的constexpr if语句 ([stmt.if]) 的子语句生成有效的特化,并且模板未实例化,或
  • [...] [...]

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_assert的模板生成有效的特化,该模板的条件是非依赖的并且计算结果为false ,因此该程序是格式false NDR。

static_assert s with a dependent condition that can evaluate to true for at least one type are not affected.具有依赖条件的static_assert不受影响,该条件可以对至少一种类型评估为true

C++20 makes static_assert in the else branch of if constexpr much shorter now, because it allows template lambda parameters. C++20 现在使if constexprelse分支中的static_assert更短,因为它允许模板 lambda 参数。 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 .因此,为了避免格式错误的情况,我们现在可以使用我们用来触发static_assertbool模板非类型参数定义一个 lambda。 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:我们立即使用()调用 lambda,但由于如果不采用else分支,则不会实例化 lambda,因此除非实际采用else分支, else不会触发断言:

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

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 TC is strictly enough. TC 的简短回答是足够严格的。

After rereading the proposal and on static_assert in the current draft , and I conclude that my worries were misguided.在重新阅读提案和 当前草案中的static_assert后,我得出结论,我的担忧被误导了。 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.如果模板被实例化,任何static_assert按预期触发。 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.因此,当实例化包含static_assert的模板时,一个static_assert(F) ,[其中 F 为假,字面上或 constexpr 值] 被丢弃的if constexpr子句将仍然“咬”。 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. static_assert上的标准文本非常短。 In standardese, it's a way to make the program ill-formed with diagnostic (as @immibis also pointed out):在standardese,它是一种方法,使具有诊断病态的程序(如@immibis还指出):

7.6 ... If the value of the expression when so converted is true, the declaration has no effect. 7.6 ... 如果如此转换时表达式的值为真,则声明无效。 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 ...否则,程序格式错误,如果提供了字符串文字,则生成的诊断消息 (1.4) 应包括字符串文本...

Your self-answer and possibly the one by TC are not quite correct.您的自我回答以及可能是 TC 的回答并不完全正确。

First of all, the sentence "Both two static asserts will fire even though within if constexpr " is not correct.首先,句子“即使在if constexpr内,两个静态断言都会触发”是不正确的。 They won't because the if constexpr condition depends on a template parameter.他们不会,因为if constexpr条件取决于模板参数。
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.您可以看到,如果您在示例代码中注释掉static_assert(false)语句和buzz()的定义: static_assert(!IntCase)不会触发并且会编译。

Furthermore, things like AlwaysFalse<T>::value or ! std::is_same_v<T, T>此外,诸如AlwaysFalse<T>::value! std::is_same_v<T, T> ! 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 . ! std::is_same_v<T, T>在丢弃的constexpr if 被允许(并且没有效果),即使没有T评估为 true
I think that "no valid specialization can be generated" is bad wording in the standard (unless cppreference is wrong; then TC would be right).我认为“无法生成有效的专业化”在标准中是不好的措辞(除非 cppreference 是错误的;那么 TC 是正确的)。 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>这与AlwaysFalse<T>::value! std::is_same_v<T, T> ! std::is_same_v<T, T> are equivalent in this context (which is what the comments to this answer are about). ! std::is_same_v<T, T>在这种情况下是等效的(这就是对此答案的评论)。
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). std::is_same和这里的非标准包装器之间的关键区别在于后者理论上可以是专门的(感谢 cigien,指出这一点并提供链接)。

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.格式错误的 NDR 是否也取决于模板是否被实例化,只是为了使这一点完全清楚。

The most concise way I've come across is to !sizeof(T*) for the condition, detailed by Raymond Chen here .我遇到的最简洁的方法是!sizeof(T*)用于条件, 由 Raymond Chen 在这里详细说明 It's a little weird, but at least it's short and doesn't require including or defining anything.这有点奇怪,但至少它很短,不需要包含或定义任何东西。 A small comment explaining it may assist future 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");
  }
}

I also came across this discussion in the old isocpp mailing list which may add to this discussion.我还在旧的 isocpp 邮件列表中遇到了这个讨论,这可能会添加到这个讨论中。 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.有人提出了一个有趣的观点,即执行这种有条件的static_assert并不总是最好的主意,因为它不能用于 SFINAE-away 重载,这有时是相关的。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM