【问题标题】:constexpr if and static_assertconstexpr if 和 static_assert
【发布时间】:2016-11-13 06:38:48
【问题描述】:

P0292R1 constexpr if 一直是included,正朝着 C++17 的方向发展。它看起来很有用(并且可以替代 SFINAE 的使用),但是关于 static_assert 的评论 格式错误,不需要诊断 在 false 分支中让我感到害怕:

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(至少是错误/未采用的分支,但实际上这意味着它不是安全或有用的事情)。

这是如何从标准文本中得出的?我发现提案措辞中没有提到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>();
}

【问题讨论】:

  • 格式不正确,因为条件为假。不是因为它在一个 constexpr if...
  • @immibis。很明显,这都是关于未采取的分支,所以我不明白你的具体意思。是否愿意根据底线问题详细说明和解释?
  • @cpplearner,完成了,但它并没有增加太多。问题在于标准的内容及其含义。
  • 目前还没有包含if constexpr措辞的标准或标准草案,而被接受的论文P0292R2也尚未公开。
  • @immibis: "但是 constexpr if(false) 会删除其中的代码。" 就是这样:它不会删除代码在未采取的分支内。它使它们成为被丢弃的语句。有区别。

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


【解决方案1】:

编辑:我将通过示例和导致此问题的误解的更详细解释来保持这个自我回答。 T.C. 的简短回答足够严格了。

在重读了提案和current draft 中的static_assert 之后,我得出结论,我的担心被误导了。首先,这里的重点应该是模板定义

格式错误;模板定义不需要诊断

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

...废弃的语句没有被实例化。

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

示例:(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
}

static_assert 上的标准文本非常短。在标准语言中,这是一种通过诊断使程序格式错误的方法(正如@immibis 也指出的那样):

7.6 ... 如果转换时表达式的值为真,则声明无效。否则,程序是非良构的,并且 生成的诊断消息 (1.4) 应包括 字符串文字,如果提供了...

【讨论】:

  • 几年过去了,您是否碰巧知道更好的解决方案? static_assert(IntCase) 对于复杂的嵌套 if-else 非常不方便。我真的很想在一些else 内打电话给static_assert(false)
  • 短语“格式错误”是格式错误的。 (没有双关语)
【解决方案2】:

这是关于模板的完善规则 - 允许编译器诊断 template&lt;class&gt; void f() { return 1; } 的相同规则。 [temp.res]/8 新更改以粗体显示:

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

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

无法为包含 static_assert 的模板生成有效的特化,该模板的条件不相关且计算结果为 false,因此该程序是非良构 NDR。

static_asserts 具有至少一种类型可以评估为 true 的相关条件不受影响。

【讨论】:

  • 特别感谢您提供草稿链接。这是有道理的,因为评估无效模板可能非常困难。
  • 再次感谢。公认。值得注意的是,您引用的行在我链接的原始提案中,但我错过了。
  • 帮助制作假模板参数dependet:template constexpr bool false_c = false;
  • 但是,我不知道这是编译器错误还是它是什么,但是,我有一个函数,它依赖于模板的不同 if constexpr 语句,以及一个 @987654329 @ 在最后一个 else 案例中。由于static_assert 不会被评估,除非其他if constexpr 语句失败,因此,由于static_assert 的评估(间接)取决于类型,我认为代码不应该格式错误,因为有“可能的输入”不会触发static_assert
  • @Peregring-lk 这是格式错误的 NDR,因为“constexpr if 语句的子语句”也会受到影响。它不是(仅)关于整个 constexpr if 语句。
【解决方案3】:

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

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

此外,诸如 AlwaysFalse&lt;T&gt;::value! std::is_same_v&lt;T, T&gt; are allowed 之类的东西在被丢弃的 constexpr if 中(并且没有效果),即使没有 T 评估为真 .
我认为“无法生成有效的专业化”在标准中是不好的措辞(除非 cppreference 是错误的;那么 T.C. 会是正确的)。它应该说“可以生成”,并进一步阐明“可以”的含义。

这与 AlwaysFalse&lt;T&gt;::value! std::is_same_v&lt;T, T&gt; are equivalent 在这种情况下的问题有关(这是这个答案的 cmets 的内容)。
我认为它们是,因为它是“可以”而不是“可以”,并且在实例化时对于所有类型都是错误的。
std::is_same 和这里的非标准包装器之间的关键区别在于后者理论上可以是专门的(感谢 cigien 指出这一点并提供链接)。

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

【讨论】:

  • 您愿意再解释一下您的观点吗?您链接的问题中的大多数答案都得出结论,即需要非 std 包装器。如stackoverflow.com/a/53945555/1149664
  • 不,据我所知,他们只讲过“类型相关”的表达式,即依赖于 T 的表达式。
  • 它工作得很好,在 gcc 或 clang 上没有警告。 wandbox.org/permlink/b7DMBGyaFj7V2Nc7
  • 这是不正确的。如果is_same_v 的特化可以存在就好了,但是“为 is_same 或 is_same_v(C++17 起)添加特化的程序的行为是未定义的。”.
  • @cigien 这不是为is_same 添加专业化。标准的可以很好地达到这个目的。 (任何其他总是产生 false 但依赖于T 的表达式也是如此。)
【解决方案4】:

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++17 中,可以在外部定义一个模板函数:template&lt;bool flag = false&gt; void static_no_match() { static_assert(flag, "no match"); },然后在else 分支中使用static_no_match()
  • 在 C++17 中,我们可以使条件依赖于模板参数,例如 static_assert(!sizeof(T), "no match");See it live
  • "else" 分支用这个 lambda 看起来很难看。
  • @korst1k 您应该可以对其进行宏处理。喜欢STATIC_FAIL("no match");
  • 我完全不确定这是否规避了 IFNDR 规则:仍然存在不存在子语句的有效专业化的情况,尽管必须推理出所选专业化的有效性是否lambda 的 operator() 为此目的而计数。
【解决方案5】:

我遇到的解决此问题的最简洁方法 (at least in current compilers) 是使用 !sizeof(T*) 作为条件 detailed by Raymond Chen here。这有点奇怪,从技术上讲,它并没有解决不正确的问题,但至少它很短,不需要包含或定义任何东西。解释它的小评论可能会对读者有所帮助:

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* 的目的是为不完整的类型提供正确的错误。

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

【讨论】:

  • 如果 constexpr 的一个分支对所有实例都无效,那么它仍然是 IFNDR。
  • 它是不是所有编译器都选择不实现的格式错误的表达式? seems to work as expected on all 3 major ones。我相信通过使其依赖于类型,它会将static_assert 延迟到if constexpr 解决之后。
  • 这就是“不需要诊断”的意思——实现可能会也可能不会做足够的分析来确定它总是无效的。 “延迟”static_assert 可能是(当今)真正的编译器中可能发生的事情,但这并不意味着任何正式的事情。
  • 感谢您澄清我的理解!您是说有一天,编译器可能会更早地进行值范围分析,并发现它总是错误的。我猜可以采取任何一种方式,实施者想要进行更多分析(就像他们在某些 UB 案例中所做的那样),或者委员会指定这是可以的(或替代方案)。编写这样的断言的愿望似乎经常出现。
  • 委员会有considered 在这里提供一个成语。他们还非正式地考虑限制问题的范围,因为从技术上讲,编译器也可以任意错误编译执行此操作的程序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-08
  • 1970-01-01
相关资源
最近更新 更多