constexpr if和static_assert

时间:2016-07-11 10:38:11

标签: c++ templates constexpr c++17 static-assert

P0292R1 constexpr if已经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中使用static_assert if(至少是假/非分支,但实际上这意味着它不安全或有用事情要做。)

这是如何从标准文本中产生的?我发现提案措辞中没有提及static_assert,而C ++ 14 constexpr函数允许static_assert(详见cppreference:constexpr)。

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

  

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

从那时起,我假设它也被禁止,不需要诊断,调用其他constexpr(模板)函数,其中某处向下调用图可能调用static_assert

底线:

如果我的理解是正确的,那么对constexpr if的安全性和有用性施加相当严格的限制,因为我们必须知道(来自文档或代码检查)static_assert的使用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>(); } 1}?我的担忧是否错位?

更新

此代码在没有警告的情况下编译(clang head 3.9.0)但是我理解格式错误,无需诊断。是否有效?

if (Modernizr.geolocation) {
    // device supports geolocation
} else {
    // device doesn't support geolocation
}

4 个答案:

答案 0 :(得分:23)

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

  

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

     
      
  • 无法为模板或子语句生成有效的专业化   一个constexpr if语句([stmt.if])   模板,模板未实例化,或
  •   
  • [...]
  •   

对于包含static_assert且条件不相关且评估为false的模板,无法生成有效的专业化,因此该程序是格式错误的NDR。

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

答案 1 :(得分:7)

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

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

答案 2 :(得分:6)

C ++ 20现在使class ViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() backButton.addTarget(self, action: #selector(back), for: .touchUpInside) navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton) } @objc func back(index : Int) { self.navigationController?.popViewController(animated: true) } } 的{​​{1}}分支中的static_assert更短,因为它允许模板lambda参数。因此,为了避免出现格式错误的情况,我们现在可以定义一个带有else模板非类型参数的lambda,以用于触发if constexpr。我们立即使用bool调用lambda,但由于如果未采用其static_assert分支将不会实例化lambda,因此除非实际采用(),否则断言不会触发:< / p>

else

答案 3 :(得分:1)

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

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

此外,在丢弃的AlwaysFalse<T>::value中,! std::is_same_v<T, T>constexpr if are allowed之类的东西(并且没有任何作用),即使没有T他们认为是正确的
我认为“无法生成有效的专业化”在标准中用词不好(除非cppreference是错误的;否则T.C.是正确的)。应该说“可以生成”,并进一步阐明“可以”的含义。

这与以下问题有关:AlwaysFalse<T>::value! std::is_same_v<T, T> are equivalent在这种情况下(这是对此答案的评论)。
我会说它们是正确的,因为它是“可以”而不是“可以”,并且在实例化时对于所有类型都是错误的。
std::is_same与非标准包装器之间的关键区别在于,非标准包装器在理论上可以是专用的(感谢cigien,指出这一点并提供链接)。

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