Why do `requires` expressions not support negative requirements?

3 days ago 2
ARTICLE AD BOX

For example, in simple concepts like:

template <typename T> concept Foo = requires (T t) { { t.bar().begin() }; { t.bar().end() }; { t.bar() } -> !std::convertible_to<std::string>; // For exposition only };

where we want to assert that something regarding the T (return value of function T::bar()) does not satisfy a requirement. And more importantly, how would you constraint, by a specific expression not being valid? Like type, that doesn't have a binary operator+:

template <typename T> concept NonAddable = requires (T t) { ! { t + t }; // For exposition only };

I know that we can rewrite like this:

template <typename T> concept Foo = requires (T t) { { t.bar().begin() }; { t.bar().end() }; requires !requires { { t.bar() } -> std::convertible_to<std::string>; }; };

But it seems a bit too much and honestly, not obvious at the first glance. Moreover, remember boolean algebra: negation of conjunction (!(E1 && E2 && ... && En)) is a disjunction (!E1 || !E2 || ... || !En). One may mistakenly start putting more that one negative expression in there and be surprised later, why this does not work as intuition tell you. So, for multiple negative requirements, you'd have to write:

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 { ...; }; };

Why not allow this:

{ expr } -> !type-constraint

or this:

{ expr } !-> type-constraint

or even this:

! { expr } -> type-constraint

Real world example, as people asking. Imagine a concept for a class that has to have a function, say description(), that must return an array of different types. Properties of those types are not important in this scope (they may have some means to be converted to a string). Type of an array container is not important, it only must be iterable. Also, the string (basic_string and all its manifestations) is an array as well, but we don't expect it there. It may be an element of an array, just not the array itself:

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 { ...; }; };

It seems so strange and counter-intuitive to me. And so simple to add. What was the reasoning not to include something like this? Wasn't it obvious that it may be useful?

Read Entire Article