c++ 具有简单要求的硬错误-表达式

nue99wik  于 2023-05-02  发布在  其他
关注(0)|答案(1)|浏览(170)

为什么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>;
                       ^

New example

von4xj4u

von4xj4u1#

float替换T会导致soft<T>()是一个无效的表达式,因为soft的约束不允许将特化命名为soft<float>
然而,表达式hard<T>()本身在float被替换为T时是有效的。然而,hard<float>的示例化是无效的,它推导出的返回类型不满足声明的返回类型的类型约束。
换句话说,在soft<T>()中,错误是在表达式中替换的直接上下文中,而对于hard<T>()则不是。
需要hard<float>的示例化来推导返回类型,以便验证表达式的有效性。例如,如果hard<float>()的返回类型是A,而没有类型约束,并且

struct A { ~A() = delete; };

hard<float>()作为包含析构函数调用的全表达式是无效的,而如果返回类型是e则不是。例如int(有或没有std::integral约束)。
如果没有hard的定义可用(只有具有受约束的auto返回类型的声明),则也不会出现硬错误。在这种情况下,当替换float时,hard<T>()成为无效表达式,因为使用未推导的占位符返回类型命名函数模板专门化的表达式无效。
没有办法验证整个函数体的有效性或检查其返回类型。这两种情况都只适用于单个表达式。在您的例子中,函数中只有一个表达式,因此可以在hard上使用requires子句来验证表达式(T{})是否具有满足概念的类型,但由于表达式的类型是平凡的,因此您的soft变体将是更简单的解决方案。返回类型的类型约束只能导致硬错误,而不会导致SFINAE。
如果你不关心“验证有效性”这部分,那么你可以 Package 一个函数来进行推导:

template<typename T>
constexpr auto hard_impl()
{ //whatever }

template<typename T>
requires std::integral<decltype(hard_impl<T>())>
constexpr auto hard()
{ return hard_impl<T>(); }

现在,如果将T替换为//whatever是病态的,则存在硬错误,但如果它仅仅导致不满足std::integral的返回类型,则没有硬错误。

相关问题