为什么requires-expression在计算最后一个静态Assert时会产生编译错误?当将T
默认为int
(并翻转其条件)时,一切似乎都很好。标准中哪里提到了这种需求表达的预期行为的差异?如何编写最后一个静态Assert,使代码在不使用任何SFINAE技术的情况下正常工作?
#include <concepts>
template<std::integral T>
constexpr auto soft() -> T
{ return T{}; }
template<typename T>
constexpr auto hard() -> std::integral auto
{ return T{}; }
static_assert(not []<typename T = float>
{ return requires { soft<T>(); }; }());
// results in hard error
static_assert(not []<typename T = float>
{ return requires { hard<T>(); }; }());
Clang生成的错误消息:
<source>:8:31: error: deduced type 'float' does not satisfy 'integral'
constexpr auto hard() -> std::integral auto
~~~~~^~~~~~~~~~~~~
<source>:16:25: note: in instantiation of function template specialization
'hard<float>' requested here
{ return requires { hard<T>(); }; }());
^
<source>:16:25: note: in instantiation of requirement here
{ return requires { hard<T>(); }; }());
^~~~~~~~~
<source>:16:40: note: in instantiation of function template specialization
'(anonymous class)::operator()<float>' requested here
{ return requires { hard<T>(); }; }());
^
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/concepts:100:24:
note: because 'is_integral_v<float>' evaluated to false
concept integral = is_integral_v<_Tp>;
^
Live example
如果没有可用的硬定义(只有具有约束自动返回类型的声明),则也不会有硬错误。在这种情况下,当替换float时,hard()将成为一个无效的表达式,因为使用未推导的占位符返回类型命名函数模板专门化的表达式是无效的。
正如对user 17732522的回答的注解中所提到的,如果在计算最后一个静态Assert之后提供函数hard
的定义,会发生什么?这是否仍然会导致硬错误?Clang和GCC似乎对此意见不一:
#include <concepts>
template<std::integral T>
constexpr auto soft() -> T
{ return T{}; }
template<typename T>
constexpr auto hard() -> std::integral auto;
static_assert(not []<typename T = float>
{ return requires { soft<T>(); }; }());
// fails on Clang -- ok on GCC
static_assert(not []<typename T = float>
{ return requires { hard<T>(); }; }());
template<typename T>
constexpr auto hard() -> std::integral auto
{ return T{}; }
Clang生成的错误消息:
<source>:8:31: error: deduced type 'float' does not satisfy 'integral'
constexpr auto hard() -> std::integral auto;
~~~~~^~~~~~~~~~~~~
<source>:16:25: note: in instantiation of function template specialization
'hard<float>' requested here
{ return requires { hard<T>(); }; }());
^
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/concepts:100:24:
note: because 'is_integral_v<float>' evaluated to false
concept integral = is_integral_v<_Tp>;
^
1条答案
按热度按时间von4xj4u1#
用
float
替换T
会导致soft<T>()
是一个无效的表达式,因为soft
的约束不允许将特化命名为soft<float>
。然而,表达式
hard<T>()
本身在float
被替换为T
时是有效的。然而,hard<float>
的示例化是无效的,它推导出的返回类型不满足声明的返回类型的类型约束。换句话说,在
soft<T>()
中,错误是在表达式中替换的直接上下文中,而对于hard<T>()
则不是。需要
hard<float>
的示例化来推导返回类型,以便验证表达式的有效性。例如,如果hard<float>()
的返回类型是A
,而没有类型约束,并且则
hard<float>()
作为包含析构函数调用的全表达式是无效的,而如果返回类型是e则不是。例如int
(有或没有std::integral
约束)。如果没有
hard
的定义可用(只有具有受约束的auto
返回类型的声明),则也不会出现硬错误。在这种情况下,当替换float
时,hard<T>()
成为无效表达式,因为使用未推导的占位符返回类型命名函数模板专门化的表达式无效。没有办法验证整个函数体的有效性或检查其返回类型。这两种情况都只适用于单个表达式。在您的例子中,函数中只有一个表达式,因此可以在
hard
上使用requires
子句来验证表达式(T{}
)是否具有满足概念的类型,但由于表达式的类型是平凡的,因此您的soft
变体将是更简单的解决方案。返回类型的类型约束只能导致硬错误,而不会导致SFINAE。如果你不关心“验证有效性”这部分,那么你可以 Package 一个函数来进行推导:
现在,如果将
T
替换为//whatever
是病态的,则存在硬错误,但如果它仅仅导致不满足std::integral
的返回类型,则没有硬错误。