失败的 static_assert 如何在 if constexpr (false) 块中工作?
P0292R1 constexpr 如果已包含,有望用于 C++17。它看起来很有用(并且可以取代 SFINAE 的使用),但是关于 false 分支中 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 中使用static_assert
(至少是 false/non-taken 分支,但这在实践中意味着这不是一个安全或有用的事情)。
这是如何从标准文本中得出的?我发现提案措辞中没有提及 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>();
}
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:
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).
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).
Is it hiding in this new sentence (after 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.
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
.
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
? 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. 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>();
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
这是关于模板的一个完善的规则 - 允许编译器诊断
template的相同规则。无效 f() { 返回 1; }
。 [temp.res]/8,新更改以粗体显示:无法为包含
static_assert
的模板生成有效的专业化,该模板的条件是非依赖的且计算结果为false
,因此该程序是格式错误的 NDR。具有至少一种类型可评估为
true
的依赖条件的static_assert
不受影响。This is talking about a well-established rule for templates - the same rule that allows compilers to diagnose
template<class> void f() { return 1; }
. [temp.res]/8 with the new change bolded:No valid specialization can be generated for a template containing
static_assert
whose condition is nondependent and evaluates tofalse
, so the program is ill-formed NDR.static_assert
s with a dependent condition that can evaluate totrue
for at least one type are not affected.C++20 现在使
if constexpr
的else
分支中的static_assert
变得更短,因为它允许模板 lambda 参数。因此,为了避免格式错误的情况,我们现在可以使用bool
模板非类型参数定义 lambda,用于触发static_assert
。我们立即使用()
调用 lambda,但由于如果不采用else
分支,则不会实例化 lambda,因此断言不会触发,除非else
实际上被采用:C++20 makes
static_assert
in theelse
branch ofif constexpr
much shorter now, because it allows template lambda parameters. So to avoid the ill-formed case, we can now define a lambda with abool
template non-type parameter that we use to trigger thestatic_assert
. We immediately invoke the lambda with()
, but since the lambda won't be instantiated if itselse
branch is not taken, the assertion will not trigger unless thatelse
is actually taken:编辑:我将保留这个自我回答,其中包含示例以及对导致此问题的误解的更详细解释。 TC 的简短回答已经足够严格了。
重读提案和 当前草案,我的结论是我的担忧是错误的。首先,这里的重点应该是模板定义。
如果模板被实例化,任何
static_assert
都会按预期触发。这大概与我引用的声明相符:这对我来说有点模糊,但我得出的结论是,这意味着出现在被丢弃语句中的模板将不会被实例化。其他代码
但在语法上必须是有效的。因此,当模板包含
static_assert
已实例化。或者(不是必需的,由编译器决定)如果已知它总是错误的,则已经在声明时。示例:(现场演示)
static_assert
上的标准文本非常明显短的。用标准语言来说,这是一种通过诊断使程序格式错误的方法(正如@immibis也指出的那样):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.If a template is instantiated, any
static_assert
fire as expected. This presumably plays well with the statement I quoted: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 discardedif constexpr
clause will thus still 'bite' when the template containing thestatic_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)
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):对于旧的编译器,这仍然是错误的,我遇到的最简洁的方法来解决这个问题(至少在当前的编译器中)是使用
!sizeof(T*)
作为条件,Raymond Chen 详细介绍。这有点奇怪,从技术上讲并不能解决格式错误的问题,但至少它很短并且不需要包含或定义任何内容。解释它的一个小注释可能会帮助读者:使用
T*
的目的是仍然为不完整的类型给出正确的错误。我还在中遇到了这个讨论旧的 isocpp 邮件列表可能会添加到此讨论中。有人提出了一个有趣的观点,即执行这种条件
static_assert
并不总是最好的主意,因为它不能用于 SFINAE-away 重载,这有时是相关的。For older compilers where this is still ill-formed, the most concise way I've come across to work-around this (at least in current compilers) is to use
!sizeof(T*)
for the condition, detailed by Raymond Chen here. It's a little weird, and doesn't technically get around the ill-formed problem, but at least it's short and doesn't require including or defining anything. A small comment explaining it may assist readers:The point of using
T*
is to still give the proper error for incomplete types.I also came across this discussion in the old isocpp mailing list which may add to this discussion. 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.已发现这是一个缺陷,CWG 2518。静态断言现在在模板声明中被忽略,因此现在被延迟到实例化。失败的静态断言不再是格式错误的,在模板解析期间无需进行诊断。
它被应用于 clang 和 所有 C++ 模式href="https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=9944ca17c0766623bce260684edc614def7ea761" rel="noreferrer">GCC 13。
This has been found to be a defect, CWG 2518. Static asserts now are ignored in template declarations, so now are delayed until instantiation. Failing static asserts are no longer ill-formed no diagnostic required during template resolution.
It is being applied to all C++ modes in clang and GCC 13.
逗号表达式可以使
static_assert
条件依赖于模板参数:或者您可以创建一个始终为 false 的依赖表达式:
A comma expression can make the
static_assert
condition dependent on template arguments:Or you can make a dependent expression which is always false:
您的自我回答以及 TC 的回答可能不太正确。
首先,“即使在
if constexpr
内,两个静态断言都会触发”这句话是不正确的。它们不会,因为if constexpr
条件取决于模板参数。您可以看到,如果您在示例代码中注释掉
static_assert(false)
语句和buzz()
的定义:static_assert(!IntCase) 不会触发并且会编译。
此外,诸如
AlwaysFalse::value
或! std::is_same_v
这与
AlwaysFalse::value
和! 是否相关的问题有关。 std::is_same_v
在这种情况下是等价的(这就是这个答案的评论的内容)。我认为它们是,因为它是“可以”而不是“可以”,并且在实例化时对于所有类型来说两者都是假的。
std::is_same
和非标准包装器之间的关键区别在于后者理论上可以是专门化的(感谢 cigien 指出这一点并提供链接)。NDR 是否格式不正确的问题也关键取决于模板是否被实例化,只是为了完全清楚这一点。
Your self-answer and possibly the one by T.C. are not quite correct.
First of all, the sentence "Both two static asserts will fire even though within
if constexpr
" is not correct. They won't because theif constexpr
condition depends on a template parameter.You can see that if you comment out the
static_assert(false)
statements and the definition ofbuzz()
in your example code:static_assert(!IntCase)
won't fire and it will compile.Furthermore, things like
AlwaysFalse<T>::value
or! std::is_same_v<T, T>
are allowed (and have no effect) inside a discardedconstexpr if
, even if there's noT
for which they evaluate to true.I think that "no valid specialization can be generated" is bad wording in the standard (unless cppreference is wrong; then T.C. would be right). 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>
are equivalent in this context (which is what the comments to this answer are about).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).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.
我的解决方案是:
My solution is: