为什么 `requires` 表达式不支持否定性要求?
例如,在简单概念中:
template <typename T>
concept Foo = requires (T t) {
{ t.bar().begin() };
{ t.bar().end() };
{ t.bar() } -> !std::convertible_to<std::string>; // For exposition only
};
在这里,我们想要断言关于 T(函数 T::bar() 的返回值)的某些内容不满足一个要求。更重要的是,如何通过一个特定表达式来约束不成立?就像一个没有二元运算符 operator+ 的类型:
template <typename T>
concept NonAddable = requires (T t) {
! { t + t }; // For exposition only
};
我知道我们可以这样重写:
template <typename T>
concept Foo = requires (T t) {
{ t.bar().begin() };
{ t.bar().end() };
requires !requires {
{ t.bar() } -> std::convertible_to<std::string>;
};
};
但这看起来有点过于复杂,老实说,一眼看去并不明显。此外,记住布尔代数:合取的否定是 析取(!(E1 && E2 && ... && En)),也就是用符号 !E1 || !E2 || ... || !En 表示的析取。有人可能会错误地把不止一个否定表达式放进去,后来会惊讶地发现,这并不像直觉所示那样起作用。因此,对于多个负面要求,你必须写:
template <typename T>
concept Foo = requires (T t) {
{ t.bar().begin() };
{ t.bar().end() };
requires !requires {
{ t.bar() } -> std::convertible_to<std::string>;
};
requires !requires {
{ t.qux() } -> std::convertible_to<int>;
};
requires !requires {
...;
};
};
为什么不允许这样:
{ expr } -> !type-constraint
或者这个:
{ expr } !-> type-constraint
甚至这个:
! { expr } -> type-constraint
现实世界的例子,正如人们所问。设想一个类的概念,必须有一个函数,比如 description(),它必须返回一个包含不同类型元素的数组。这些类型的属性在此范围内并不重要(它们可能有某种将其转换成字符串的方式)。数组容器的类型并不重要,它只需要是可迭代的。另外,字符串(basic_string 及其所有表现形式)也是一种数组,但我们并不期望它出现在那里。它可以是数组的一个元素,而不是数组本身:
template <typename T>
concept Descriptive = requires (T t) {
{ t.description().begin() };
{ t.description().end() };
requires !requires {
{ t.description() } -> std::convertible_to<std::string>;
};
requires !requires {
{ t.description() } -> std::convertible_to<std::wstring>;
};
requires !requires {
...;
};
};
在我看来,这看起来既奇怪又违反直觉,但又如此容易添加。为什么不包含这样的东西?难道这不是显而易见的,它可能有用吗?
解决方案
如今的Concepts提案本身就是一项庞大且推进缓慢的工作。团队努力避免在没有强有力证据表明需要时增加额外的复杂性。你提出的这类负向约束显然对标准库并不有用,因此它们没有被纳入我们现在拥有的初始版本(也称为“Concepts Lite”)。
如果你能提供真实、有价值的用例证据,那么也许你可以参与标准化过程,提出一个经过深思熟虑、对你和他人有用的提案。不过,这并非轻而易举的事。