constexpr if 和 static_assert

constexpr if and static_assert

提问人:Johan Lundberg 提问时间:7/11/2016 最后编辑:Johan Lundberg 更新时间:10/13/2023 访问量:25239

问:

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 中使用 if(至少是 false / non-taken 分支,但这在实践中意味着它不是一件安全或有用的事情)。static_assert

这是如何从标准文本中得出的?我发现提案措辞中没有提到,C++14 constexpr 函数确实允许(详细信息在 cppreference:constexpr)。static_assertstatic_assert

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

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

从那时起,我假设也禁止调用其他 constexpr(模板)函数,这些函数在调用图的某个位置可能会调用。static_assert

底线:

如果我的理解是正确的,那不是对我们必须知道的安全性和有用性(从文档或代码检查中)关于任何使用吗?我的担心是不是放错了地方?constexpr ifstatic_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>();
}
C++ 模板 constexpr c++17 static-assert

评论

1赞 user253751 7/11/2016
它的格式不正确,因为条件是假的。不是因为它在 constexpr 中,如果......
3赞 Johan Lundberg 7/11/2016
@immibis。很明显,这都是关于未采取的分支,所以我不明白你的具体意思。想从底线问题的角度来阐述和解释吗?
3赞 Johan Lundberg 7/11/2016
@cpplearner,做到了,但它并没有增加太多。问题在于该标准的内容及其含义。
1赞 cpplearner 7/11/2016
目前还没有包含 的措辞的标准或标准草案,P0292R2,被接受的论文,也尚未公开。if constexpr
1赞 Nicol Bolas 7/11/2016
@immibis:“但是 constexpr if(false) 删除了其中的代码。事情就是这样:它不会删除未采用的分支中的代码。它使它们成为丢弃的语句。这是有区别的。

答:

10赞 Johan Lundberg 7/12/2016 #1

编辑:我保留了这个自我回答,并举例说明了导致这个问题的误解。T.C.的简短回答已经足够了。

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

格式不正确;无需对模板进行诊断

如果实例化了模板,则按预期触发任何模板。这大概与我引用的陈述相吻合:static_assert

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

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

标准文本非常短。在标准中,这是一种使程序格式不正确的诊断方法(正如@immibis还指出的那样):static_assert

7.6 ...如果转换时表达式的值为 true,则声明无效。否则,程序格式不正确,并且 生成的诊断消息 (1.4) 应包括 string-literal,如果提供...

评论

3赞 javaLover 3/5/2019
几年过去了,你碰巧知道更好的解决方案吗? 对于复杂的 nest if-else 来说非常不方便。我真的很想打电话给一些人.static_assert(IntCase)static_assert(false)else
0赞 L. F. 5/28/2019
短语“格式错误”格式不正确。(没有双关语的意思)
45赞 T.C. 7/12/2016 #2

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

如果出现以下情况,则程序格式不正确,无需诊断:

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

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

static_assert具有可计算为至少一种类型的从属条件的 s 不受影响。true

评论

0赞 Johan Lundberg 7/12/2016
特别感谢您的草稿链接。这是有道理的,因为评估无效模板可能非常困难。
0赞 Johan Lundberg 7/13/2016
再次感谢。接受。值得注意的是,您引用的台词在我链接到的原始提案中,但我错过了它们。
0赞 Benjamin Buch 6/17/2017
帮助程序使 false 模板参数 dependet:template < typename > constexpr bool false_c = false;
0赞 ABu 10/27/2017
但是,我不知道这是否是编译器错误或它是什么,但是,我有一个函数,该函数确实依赖于模板的不同语句,以及最后一种情况。由于除非其他语句失败,否则不会计算 ,因此,由于对 的计算(间接)取决于类型,我认为代码不应该格式错误,因为有“可能的输入”不会触发 .if constexprstatic_assert(false)elsestatic_assertif constexprstatic_assertstatic_assert
0赞 philipp2100 9/16/2020
@Peregring-lk 这是格式不正确的 NDR,因为“constexpr 的子语句 if 语句”也会受到影响。它不仅(仅)与整个 constexpr if 语句有关。
0赞 philipp2100 3/22/2020 #3

你的自我回答,可能还有T.C.的回答,都不太正确。

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

此外,像 or 这样的东西在丢弃的 a 中是允许的(并且没有影响),即使没有 T 可以计算为真
我认为“不能生成有效的专业化”是标准中的糟糕措辞(除非 cppreference 是错误的;那么 T.C. 是对的)。它应该说“可以生成”,并进一步澄清“可能”的含义。
AlwaysFalse<T>::value! std::is_same_v<T, T>constexpr if

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

NDR 格式是否错误的问题也在很大程度上取决于模板是否实例化,只是为了完全清楚这一点。

评论

0赞 Johan Lundberg 3/22/2020
你愿意更多地解释你的观点吗?您链接到的问题中的大多数答案都得出结论,非 std 包装器是必需的。如 stackoverflow.com/a/53945555/1149664
0赞 philipp2100 3/24/2020
不,据我所知,他们只谈到了“类型依赖”表达式,即依赖于 T 的表达式。
0赞 Martin Morterol 6/19/2020
它工作得很好,没有对 gcc 或 clang 发出警告。wandbox.org/permlink/b7DMBGyaFj7V2Nc7
0赞 cigien 9/7/2020
这是不正确的。如果可以存在专业化,那就没问题了,但是“为is_same或is_same_v添加专业化的程序的行为(自 C++17 以来)是未定义的。is_same_v
0赞 philipp2100 9/8/2020
@cigien 这不是要为 . 添加专业化。标准的可以很好地达到这个目的。(任何其他总是产生 false 但依赖于 的表达式也是如此。is_sameT
37赞 Nikos C. 10/14/2020 #4

C++20 现在使分支更短,因为它允许模板 lambda 参数。因此,为了避免出现格式错误的情况,我们现在可以使用模板非类型参数定义一个 lambda,该参数用于触发 .我们立即使用 调用 lambda,但由于 lambda 不会被实例化,如果它的分支没有被采用,因此除非实际采用,否则不会触发断言:static_assertelseif constexprboolstatic_assert()elseelse

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

评论

7赞 cxxl 11/27/2020
在 C++17 中,可以在 outside : 定义一个模板函数,然后在分支中使用。template<bool flag = false> void static_no_match() { static_assert(flag, "no match"); }static_no_match()else
16赞 Ruslan 5/25/2021
在 C++17 中,我们可以使条件依赖于模板参数,例如 .现场观看static_assert(!sizeof(T), "no match");
4赞 korst1k 11/6/2021
“else”分支使用此 lambda 看起来很丑陋。
3赞 Davis Herring 11/11/2021
我完全不确定这是否规避了 IFNDR 规则:仍然不存在子语句的有效专业化,尽管人们必须推理出所选 lambda 专业化的有效性是否被计算在内。operator()
6赞 interjay 2/15/2023
这个答案在P2593委员会的文件中被引用,被描述为“可怕和错误”。该论文已被 C++23 接受,并将允许 .static_assert(false)
4赞 golvok 11/11/2021 #5

我遇到的最简洁的解决方法(至少在当前的编译器中)是用于该条件,Raymond Chen 在此处详细介绍了这种情况。这有点奇怪,从技术上讲并不能解决格式错误的问题,但至少它很短,不需要包含或定义任何东西。解释它的小评论可能会对读者有所帮助:!sizeof(T*)

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 邮件列表中遇到了这个讨论,这可能会增加这个讨论。有人提出了一个有趣的观点,即做这种条件并不总是最好的主意,因为它不能用于 SFINAE-away 重载,这有时是相关的。static_assert

评论

4赞 Davis Herring 11/11/2021
如果 constexpr 的分支对所有实例都无效,那么它仍然是 IFNDR。
0赞 golvok 11/12/2021
这是一个格式错误的表达式,所有编译器都选择不实现吗?似乎在所有 3 个主要项目上都按预期工作。我相信通过使其依赖于类型,它可以延迟到解决之后。static_assertif constexpr
2赞 Davis Herring 11/12/2021
这就是“无需诊断”的意思 - 实现可能会或可能不会进行足够的分析以确定它始终无效。“延迟”可能是(当今)真实编译器中可能发生的事情,但它在形式上根本没有任何意义。static_assert
0赞 golvok 11/12/2021
感谢您澄清我的理解!你是说有一天,编译器可能会更早地进行值范围分析,并发现它总是错误的。我猜可以采取任何一种方式,要么是实施者想要做更多的分析(就像他们在一些 UB 案例中所做的那样),要么是委员会指定这是可以的(或替代方案)。像这样的写作欲望似乎经常出现。
2赞 Davis Herring 11/12/2021
委员会已考虑在此提供一个成语。他们还非正式地考虑限制问题的范围,因为从技术上讲,编译器也可以任意错误地编译执行此操作的程序。
-3赞 user10302409 11/25/2022 #6

我的解决方案是:

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

评论

0赞 Osyotr 11/27/2022
这是“格式错误,无需诊断”。阅读其他答案,原因。
3赞 golvok 6/23/2023 #7

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

它正在应用于 clangGCC 13 中的所有 C++ 模式。

0赞 leek 10/13/2023 #8

逗号表达式可以使条件依赖于模板参数: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");