为什么 C++ requires 表达式没有专门的否定要求语法?
结论
C++20 已经能够表达这些否定条件:对类型性质取反可以使用 nested requirement;检查表达式是否无效,可以对整个 requires 表达式取反。原问题中的多层嵌套并非每次都需要。
{ expr } -> !Concept 不是标准语法。-> 后面要求的是 type-constraint,不能直接放逻辑非表达式。现有规则可以解释语法如何工作,但无法说明委员会当时为什么没有加入这些简写;要确认历史动机,需要对应的提案或讨论记录支持。C++ 草案:compound requirements
返回类型不满足 concept:直接写 nested requirement
对于 Foo,可以直接对 std::convertible_to 取反,不必再嵌套一层 requires 表达式:
#include <concepts>
#include <string>
template <typename T>
concept Foo = requires (T t) {
t.bar().begin();
t.bar().end();
requires (!std::convertible_to<
decltype((t.bar())), std::string>);
};
requires 条件; 是 nested requirement,要求其中的约束表达式成立。前两行检查相应表达式是否合法。C++ 草案:nested requirements
使用 decltype((expr)) 是为了与 compound requirement 的规则一致:{ expr } -> C<Args...>; 将 C 应用于 decltype((expr)),并接上显式提供的模板实参。对于这里的函数调用,decltype(t.bar()) 也能得到所需类型。C++ 草案:返回类型要求
表达式必须无效:否定 requires 表达式
如果要求 t + t 不合法,可以直接定义:
template <typename T>
concept NonAddable = !requires (T t) {
t + t;
};
如果它只是较大 concept 中的一项要求,也可以放在外层 requires 内:
template <typename T>
concept NonAddable = requires (T t) {
requires (!requires { t + t; });
};
在这种依赖模板参数的检查中,表达式代换失败会使内层 requires 表达式得到 false,取反后得到 true。但这不意味着 requires 能捕获任何编译错误:非模板上下文中的非法表达式,以及检查引发的某些模板实例化错误,仍可能导致编译失败。C++ 草案:requires expressions
NonAddable 检查的是局部参数 t 相加的表达式是否有效。这里的 t 是左值,检查结果不等同于“类型没有声明任何 operator+”。隐式转换、重载是否可访问和操作数的值类别都可能影响结果。
想保留箭头写法:定义一个否定 concept
type-constraint 所引用的 concept 可以自行定义为否定条件。如果需要排除多个目标类型,可以写成:
#include <concepts>
#include <string>
template <typename From, typename... To>
concept NotConvertibleToAny =
(!std::convertible_to<From, To> && ...);
template <typename T>
concept Descriptive = requires (T t) {
t.description().begin();
t.description().end();
{ t.description() }
-> NotConvertibleToAny<std::string, std::wstring>;
};
这里的 fold expression 对每个 std::convertible_to 分别取反,再用 && 连接。箭头前的表达式仍必须合法,其类型也必须满足这个自定义 concept。
自定义否定 concept 要求表达式合法,同时类型性质不成立;!requires { { expr } -> C; } 则表示这项 compound requirement 不成立,也包括 expr 本身不合法的情况。原问题中的其他成员访问检查已经要求 bar() 或 description() 可用,但单独使用时仍需区分这两种含义。
多个否定条件与字符串过滤的边界
对德摩根律的担忧是正确的。假设 E1、E2 表示两项要求是否成立:
!(E1 && E2) == !E1 || !E2
把多个要求放进同一个被否定的 requires 表达式,只要求其中至少一项失败。要让每项都失败,应分别否定,或者像上面的示例一样使用 (!条件 && ...)。
Descriptive 示例的检查也有边界:
begin()和end()调用合法,并不足以证明返回值满足完整的迭代器或 range 要求,也没有检查元素是否具有不同类型。- 排除满足
std::convertible_to<std::string>和std::convertible_to<std::wstring>的类型,不等于排除全部std::basic_string特化。它也可能排除提供相应转换的其他容器。 std::convertible_to要求隐式转换和显式转换均可用,并附带语义要求;否定它并不表示禁止所有形式的字符串构造或显式转换。C++ 草案:convertible_to
如果需要排除所有 std::basic_string 特化,应另行定义识别该模板特化的类型特征,再对该特征取反。否定的写法可以复用,但被否定的条件必须准确对应业务要求。
备注:内容仅供参考。
