提问人:Johan Lundberg 提问时间:7/11/2016 最后编辑:Johan Lundberg 更新时间:10/13/2023 访问量:25239
constexpr if 和 static_assert
constexpr if and static_assert
问:
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_assert
static_assert
它是否隐藏在这个新句子中(在 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>();
}
答:
编辑:我保留了这个自我回答,并举例说明了导致这个问题的误解。T.C.的简短回答已经足够了。
在重新阅读了该提案和当前草案之后,我得出结论,我的担忧是错误的。首先,这里的重点应该放在模板定义上。static_assert
格式不正确;无需对模板进行诊断
如果实例化了模板,则按预期触发任何模板。这大概与我引用的陈述相吻合:static_assert
...不会实例化丢弃的语句。
这对我来说有点模糊,但我得出的结论是,这意味着丢弃语句中出现的模板不会被实例化。其他代码
但是,必须在语法上有效。因此,当包含 的模板被实例化时,丢弃子句中的 A , [其中 F 为 false,无论是字面上还是 constexpr 值] 仍将“咬”。或者(不需要,由编译器摆布)已经在声明中,如果已知它总是假的。static_assert(F)
if constexpr
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
}
标准文本非常短。在标准中,这是一种使程序格式不正确的诊断方法(正如@immibis还指出的那样):static_assert
7.6 ...如果转换时表达式的值为 true,则声明无效。否则,程序格式不正确,并且 生成的诊断消息 (1.4) 应包括 string-literal,如果提供...
评论
static_assert(IntCase)
static_assert(false)
else
这是在谈论一个完善的模板规则 - 允许编译器诊断 .[temp.res]/8 的新更改以粗体显示:template<class> void f() { return 1; }
如果出现以下情况,则程序格式不正确,无需诊断:
- 无法为模板或子语句生成有效的专用化
的 constexpr
if 语句 ([stmt.if]) 中 模板,并且模板未实例化,或者- [...]
无法为包含其条件为非依赖且计算结果为 的模板生成有效的专用化,因此程序的 NDR 格式不正确。static_assert
false
static_assert
具有可计算为至少一种类型的从属条件的 s 不受影响。true
评论
if constexpr
static_assert(false)
else
static_assert
if constexpr
static_assert
static_assert
你的自我回答,可能还有T.C.的回答,都不太正确。
首先,“两个静态断言都会触发,即使 within ”这句话是不正确的。他们不会,因为条件取决于模板参数。
您可以看到,如果注释掉示例代码中的语句和定义: 不会触发,它将编译。if constexpr
if constexpr
static_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 格式是否错误的问题也在很大程度上取决于模板是否实例化,只是为了完全清楚这一点。
评论
is_same_v
is_same
T
C++20 现在使分支更短,因为它允许模板 lambda 参数。因此,为了避免出现格式错误的情况,我们现在可以使用模板非类型参数定义一个 lambda,该参数用于触发 .我们立即使用 调用 lambda,但由于 lambda 不会被实例化,如果它的分支没有被采用,因此除非实际采用,否则不会触发断言:static_assert
else
if constexpr
bool
static_assert
()
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");}();
}
评论
template<bool flag = false> void static_no_match() { static_assert(flag, "no match"); }
static_no_match()
else
operator()
我遇到的最简洁的解决方法(至少在当前的编译器中)是用于该条件,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
评论
static_assert
if constexpr
static_assert
我的解决方案是:
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.");
评论
这已被发现是一个缺陷,CWG 2518。静态断言现在在模板声明中被忽略,因此现在被延迟到实例化。失败的静态断言不再是格式错误,在模板解析期间无需诊断。
它正在应用于 clang 和 GCC 13 中的所有 C++ 模式。
逗号表达式可以使条件依赖于模板参数: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");
评论
if constexpr