[ Example:
template <class T>
concept bool C() {
return requires(T a, T b, const T c, const T d) {
c == d; // #1
a = std::move(b); // #2
a = c; // #3
};
}
Expression #1 does not modify either of its operands, #2 modifies both of its operands, and #3 modifies
only its first operand a.
Expression #1 implicitly requires additional expression variants that meet the requirements for c == d
(including non-modification), as if the expressionshad been declared as well.
a == d; a == b; a == move(b); a == d;
c == a; c == move(a); c == move(d);
move(a) == d; move(a) == b; move(a) == move(b); move(a) == move(d);
move(c) == b; move(c) == move(b); move(c) == d; move(c) == move(d);
Expression #3 implicitly requires additional expression variants that meet the requirements for a = c (including non-modification of the second operand), as if the expressions a = b and a = move(c) had been declared. Expression #3 does not implicitly require an expression variant with a non-constant rvalue second
operand, since expression #2 already specifies exactly such an expression explicitly. —end example ]
[ Example: The following type T meets the explicitly stated syntactic requirements of concept C above but
does not meet the additional implicit requirements:
struct T {
bool operator==(const T&) const { return true; }
bool operator==(T&) = delete;
};
T fails to meet the implicit requirements of C, so C<T>() is not satisfied. Since implementations are not
required to validate the syntax of implicit requirements, it is unspecified whether or not an implementation
diagnoses as ill-formed a program which requires C<T>(). —end example ]
This looks like, Concepts Lite, as currently specified, did not provide a way to clearly and concisely express syntactic (only syntactic) requirements of Ranges TS, and it had to resort to English text in the paper. Should we draw a conclusion form this, that something is lacking in Concepts Lite?
This looks like, Concepts Lite, as currently specified, did not provide a way to clearly and concisely express syntactic (only syntactic) requirements of Ranges TS, and it had to resort to English text in the paper. Should we draw a conclusion form this, that something is lacking in Concepts Lite?
So... as a hastily drawn up strawman:template<typename T>concept bool C = requires (T x) {try { f(x) } -> bool; // try check the overload set};struct S { };bool f(S);bool f(S&);bool f(S&&);bool f(const S&);bool f(const S&&);
- Maybe we want this to be the default rule for checking. Needs more thought. Experimentation would be good.
As a historical footnote, C++0x concepts had this problem too. It was easy to specify requirements for what was generally expected, but to fully cover all cases, you would still need to enumerate combinations of operand types. The problem there was worse because it potentially affected runtime performance. A good description of the problem and its solution is here: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2576.pdf.
--
You received this message because you are subscribed to the Google Groups "SG8 - Concepts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to concepts+unsubscribe@isocpp.org.
To post to this group, send email to conc...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/concepts/.
Looking at C++ Extensions for Ranges (N4622), it was necessary to devise a notion of "implicit constraints". In 4.1.1/6, we read:
Where a requires-expression declares an expression that is non-modifying for some constant lvalue operand, additional variants of that expression that accept a non-constant lvalue or (possibly constant) rvalue for the given operand are also required except where such an expression variant is explicitly required with differing semantics. Such implicit expression variants must meet the semantic requirements of the declared expression. The extent to which an implementation validates the syntax of these implicit expression variants is unspecified.
[snip]
This looks like, Concepts Lite, as currently specified, did not provide a way to clearly and concisely express syntactic (only syntactic) requirements of Ranges TS, and it had to resort to English text in the paper. Should we draw a conclusion form this, that something is lacking in Concepts Lite?
This looks like, Concepts Lite, as currently specified, did not provide a way to clearly and concisely express syntactic (only syntactic) requirements of Ranges TS, and it had to resort to English text in the paper. Should we draw a conclusion form this, that something is lacking in Concepts Lite?I would conclude that people writing pathological classes make my life harder. BTW, it's just plain Concepts now.Maybe there's something we can do about this. The mechanism for checking these requirements are the usual lookup and overload resolution rules. Resolution guarantees that a single candidate can be found for the arguments. These implicit constraints really want to evaluate all the viable candidates, not just the one selected by overload resolution.So... as a hastily drawn up strawman:template<typename T>concept bool C = requires (T x) {try { f(x) } -> bool; // try check the overload set};struct S { };bool f(S);bool f(S&);bool f(S&&);bool f(const S&);bool f(const S&&);static_assert(C<S>);When we check for f(x), the "try" means that all viable candidates matching f(x) must:- have a return convertible to bool,- be accessible (for member functions),- and be non-deleted (because the candidate might be selected during instantiation).So, since x is an lvalue, every overload is viable, all return bool, and none are deleted. The assertion holds. Change one of those declarations to, say, bool f(S&) = delete, and the assertion fails.Some thoughts about this approach.- This avoids generating all possible constraints based on different combinations of parameter types (that's an n! algorithm). This approach is linear in the size of the viable candidate set.- I think the granularity is right. This should be applied to the entire requirement, and not to individual operands. Doing otherwise would necessitate a new kind of type (universal references anyone?), new deduction rules, and a change to overload resolution that allows ambiguous overloads to be "okay" in this context.- Maybe we want this to be the default rule for checking. Needs more thought. Experimentation would be good.
So... as a hastily drawn up strawman:template<typename T>concept bool C = requires (T x) {try { f(x) } -> bool; // try check the overload set};
If we make this rule the default, then many useful notions like:template<class T>concept bool C1 = requires(T t, T const ct) {{ &t } -> T*;{ &ct } -> T const*;};template<class T, class U>concept bool C2 = requires(T t, T const ct) {{ get(t) } -> Same<U>&;{ get(ct) } -> Same<U const>&;};become self-contradictory and therefore unimplementable.
struct S { };bool f(S);bool f(S&);bool f(S&&);bool f(const S&);bool f(const S&&);I know it's a strawman, but IIUC, checking all of these for every combination of N arguments of each associated function would probably not be desirable, as it can imply doing substitution for each of those combinations, which can explode pretty quickly (i.e. imagine that f is one or more function templates and the amount of instantiations that might be involved).
You don't need to check combinations (that would be a non-starter for me). This approach just builds the overload set once, filters non-viable candidates, and then checks each to determine accessibility and non-deletedness.Ah, I see what you're describing now, sorry for misinterpreting. This would be pretty unique from a normal SFINAE-like check, but the result might be reasonable. It's hard for me to grasp all of the implications immediately, but it seems interesting to experiment with, even if it's not a default.
template<class T>
concept bool C1 = requires(T t, T const ct) {
try { &t } -> T*; // address of T& or weaker converts to T*
try { &ct } -> T const*; // address of const T& or weaker converts to T const*
};
template<class T, class U>concept bool C2 = requires(T t, T const ct) {{ get(t) } -> Same<U>&;{ get(ct) } -> Same<U const>&;};
template<class T, class U>
concept bool C2 = requires(T t, T const ct, __Treat_as_value const int i) {
{ get_at_index(t, i) } -> Same<U>&;
{ get_at_index(ct, i) } -> Same<U const>&;
};
template<class T, class U>
concept bool C2 = requires(T & t, T const& ct, int i) {
{ get_at_index(t, i) } -> Same<U>&;
{ get_at_index(ct, i) } -> Same<U const>&;
};
W dniu środa, 22 lutego 2017 18:36:55 UTC+1 użytkownik Casey Carter napisał:On Wednesday, February 22, 2017 at 6:39:46 AM UTC-8, Andrew Sutton wrote:- Maybe we want this to be the default rule for checking. Needs more thought. Experimentation would be good.If we make this rule the default, then many useful notions like:template<class T>concept bool C1 = requires(T t, T const ct) {{ &t } -> T*;{ &ct } -> T const*;};
I think the above would work with "Andrew's extension":
template<class T>
concept bool C1 = requires(T t, T const ct) {
try { &t } -> T*; // address of T& or weaker converts to T*
try { &ct } -> T const*; // address of const T& or weaker converts to T const*
};
T* operator&(T&);
const T* operator&(const T&);
template<class T, class U>concept bool C2 = requires(T t, T const ct) {{ get(t) } -> Same<U>&;{ get(ct) } -> Same<U const>&;};
Ok, this one would not, but is this Same<> also not a workaround for a missing language construct?
template <class U>
requires Same<U, T>()
void f(U);
using W = decltype(f(E));
E; requires Same<decltype((E)), T>();
{ E } -> requires Same<T>&&;
It seams that this inclusion of additional overloads only applies to inputs that are used for observing the objects value. It looks like what we should be annotating is not expressions but inputs:
template<class T, class U>
concept bool C2 = requires(T t, T const ct, __Treat_as_value const int i) {
{ get_at_index(t, i) } -> Same<U>&;
{ get_at_index(ct, i) } -> Same<U const>&;
};
This treats t and ct literally, but considers different cv/ref variants for i.
Or, a bit more intrusive change: treat reference arguments literally, and treat non-reference arguments with all cv/ref combinations:
template<class T, class U>
concept bool C2 = requires(T & t, T const& ct, int i) {
{ get_at_index(t, i) } -> Same<U>&;
{ get_at_index(ct, i) } -> Same<U const>&;
};
Now, t and ct are references (treated literally), and i is a value (all cv/ref variants considered).