flo*_*tan 13 c++ templates static-assert language-lawyer c++17
当我们想在 astatic_assert
中使用 a 时,if constexpr
我们必须使条件依赖于某个模板参数。有趣的是,当代码包含在 lambda 中时,gcc 和 clang 不同意。
以下代码使用 gcc 进行编译,但 clang 会触发断言,即使if constexpr
不能为真。
#include <utility>
template<typename T> constexpr std::false_type False;
template<typename T>
void foo() {
auto f = [](auto x) {
constexpr int val = decltype(x)::value;
if constexpr(val < 0) {
static_assert(False<T>, "AAA");
}
};
f(std::integral_constant<int, 1>{});
}
int main() {
foo<int>();
}
Run Code Online (Sandbox Code Playgroud)
它可以很容易地通过用固定False<T>
通过False<decltype(x)>
。
所以问题是:哪个编译器是正确的?我认为 gcc 是正确的,因为 中的条件static_assert
取决于T
,但我不确定。
Bar*_*rry 13
这里通常的规则是[temp.res]/8:
程序格式错误,无需诊断,如果: 无法为模板或 constexpr 的子语句生成有效的特化,如果模板中的语句和模板未实例化
实例化后foo<T>
,static_assert
您拥有的不再依赖。它变成static_assert(false)
- 对于泛型 lambda 的调用运算符的所有可能实例化f
。这是格式错误的,不需要诊断。Clang 诊断,gcc 没有。两者都是正确的。
请注意,它并不重要的是,static_assert
这里被丢弃。
它可以很容易地通过用固定
False<T>
通过False<decltype(x)>
。
这将static_assert
依赖项保留在泛型 lambda 中,现在我们进入一种状态,假设可能存在有效的特化,因此我们不再是格式错误的,ndr。
来自[stmt.if]/2(强调我的)
\n\n\n\n\n如果 if 语句的形式为 if constexpr,则条件的值应为根据上下文转换的 bool 类型常量表达式;这种形式称为 constexpr if 语句。如果转换后的条件的值为 false,则第一个子语句是被丢弃的语句,否则第二个子语句(如果存在)是被丢弃的语句。在封闭模板化实体 ([temp.pre]) 的实例化期间,如果条件在其实例化后不依赖于值,则不会实例化丢弃的子语句(如果有)。
\n
读到这一点,人们会认为静态断言将被删除,但事实并非如此。
\n\n静态断言在模板的第一阶段被触发,因为编译器知道它总是错误的。
\n\n来自[temp.res]/8(强调我的)
\n\n\n\n\n可以在任何实例化之前检查模板的有效性。[注意:知道哪些名称是类型名称可以以这种方式检查每个模板的语法。\xe2\x80\x94尾注]\n 如果出现以下情况,则程序格式错误,无需诊断:
\n\n\n
\n\n- (8.1) 无法为模板或模板内 constexpr if 语句的子语句生成有效的特化,并且模板未实例化,或者
\n[...]
\n
是的,确实,你的False<T>
依赖T
。问题是泛型 lambda 本身就是一个模板,并且False<T>
不依赖于 lambda 的任何模板参数。
对于T
false False<T>
,静态断言将始终为 false,无论将哪个模板参数发送到 lambda。
编译器可以看到,对于 template 的任何实例化operator()
,静态断言将始终为当前 T 触发。因此会出现编译器错误。
解决这个问题的方法是依赖于x
:
template<typename T>\nvoid foo() {\n\n auto f = [](auto x) {\n if constexpr(x < 0) {\n static_assert(False<decltype(x)>, "AAA");\n }\n };\n\n f(std::integral_constant<int, 1>{});\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n\n