| Thread overview | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
November 20, 2011 Concepts vs template constraints - the practical approach | ||||
|---|---|---|---|---|
| ||||
Hi there, back in the discussions about C++-"concepts" it was argued that D-template-parameter constraints allow you to achieve the same goal. Now, I find it fairly difficult to come up with a clean solution for this that actually scales up for complex libraries. My best attempt so far is as follows: =================================================== template verifyMyConcept(A) { static assert(is(A.type)); static assert(A.len >= 0); static assert(is(typeof(A.init[0]) == A.type)); } struct MyClass(T,int R) { alias T type; enum len = R; T[R] value; T opIndex(int idx) { return value[idx]; } mixin verifyMyConcept!(typeof(this)); } void myfunction(A)(A arr) if(__traits(compiles, verifyMyConcept!(A))) { } unittest { MyClass!(int,4) x; mixin verifyMyConcept!(typeof(x)); myfunction(x); } =================================================== As you can see, this approach attempts to define all the requirements for MyConcept in one place as individual static assertions. This permits error messages to identify which requirement for the concept is not met. Still the code seems fairly ugly to me and the error message is not quite clear enough for my taste. Ideally, there should be a way to use "concepts" similar to interfaces: a) A concept should be defined readably in one place, listing a set of requirements, possibly inheriting other concepts. b) A struct implementing the concept should state this in a similar way to a class that implements an interface. c) A template that requires a parameter to fulfil a concept should state this in a similar way to a function requiring a specific input type and most importantly: d) a user of the library should get a clear and simple error message when using templates with parameters that do not fulfil the required concept. Has anyone achieved these goals better than my feeble attempt? Greetings, Norbert | ||||
November 20, 2011 Re: Concepts vs template constraints - the practical approach | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Norbert Nemec | > void myfunction(A)(A arr)
> if(__traits(compiles, verifyMyConcept!(A)))
> {
> }
It should be
---
void myfunction(A)(A arr)
{
verifyMyConcept!A;
}
---
to see the error messages.
| |||
November 20, 2011 Re: Concepts vs template constraints - the practical approach | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Norbert Nemec | Your example, a bit improved:
---
// In std.concept, e.g.
template verify(alias concept, T) {
//static assert(isConcept!concept, concept.stringof ~ " is not a concept"); //the test for particular concept's concepts can be added
mixin concept!T;
}
template verify(alias concept) {
mixin verify!(concept, typeof(this));
}
template satisfy(alias concept, T) {
enum satisfy = __traits(compiles, verify!(concept, T));
}
// User code
template myConcept(A) {
static assert(is(A.type), "Error 1: !is(A.type)");
static assert(A.len >= 0, "Error 2: A.len < 0");
static assert(is(typeof(A.init[0]) == A.type), "Error 3: typeof(A.init[0]) != A.type");
}
struct MyClass(T,int R) {
alias T type;
enum len = R;
T[R] value;
T opIndex(int idx) {
return value[idx];
}
mixin verify!myConcept;
}
void myFunction(A)(A arr) {
mixin verify!(myConcept, A);
}
void myOverlodedFunction(A)(A arr) if(satisfy!(myConcept, A)) {
// Do something
}
void myOverlodedFunction(A)(int i, A arr) if(satisfy!(myConcept, A)) {
// Do something
}
void myOverlodedFunction(T...)(T) {
static assert(0, "Error: here should be some user-defined error message(s) based on T");
}
unittest {
MyClass!(int,4) x;
myFunction(x);
//myFunction(1); //Error: static assert "Error 1: !is(A.type)"
myOverlodedFunction(x);
myOverlodedFunction(3, x);
//myOverlodedFunction(3, 2); //Error: static assert "Error: here should be..."
}
---
| |||
November 20, 2011 Re: Concepts vs template constraints - the practical approach | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Norbert Nemec | On 11/20/2011 08:41 AM, Norbert Nemec wrote: > Hi there, > > back in the discussions about C++-"concepts" it was argued that > D-template-parameter constraints allow you to achieve the same goal. Have a look at std.range.hasLength, std.range.isInputRange, and friends. > Now, I find it fairly difficult to come up with a clean solution for > this that actually scales up for complex libraries. My best attempt so > far is as follows: > > =================================================== > > template verifyMyConcept(A) { > static assert(is(A.type)); > static assert(A.len >= 0); > static assert(is(typeof(A.init[0]) == A.type)); > } > > struct MyClass(T,int R) { > alias T type; > enum len = R; > > T[R] value; > > T opIndex(int idx) { > return value[idx]; > } > > mixin verifyMyConcept!(typeof(this)); > } > > void myfunction(A)(A arr) > if(__traits(compiles, verifyMyConcept!(A))) > { > } > > unittest { > MyClass!(int,4) x; > > mixin verifyMyConcept!(typeof(x)); > myfunction(x); > } > > =================================================== > > As you can see, this approach attempts to define all the requirements > for MyConcept in one place as individual static assertions. This permits > error messages to identify which requirement for the concept is not met. > > Still the code seems fairly ugly to me and the error message is not > quite clear enough for my taste. > > Ideally, there should be a way to use "concepts" similar to interfaces: > > a) A concept should be defined readably in one place, listing a set of > requirements, possibly inheriting other concepts. I have separated the parts of the concepts below and then defined matchesMyConcept to "inherit" them. > b) A struct implementing the concept should state this in a similar way > to a class that implements an interface. > > c) A template that requires a parameter to fulfil a concept should state > this in a similar way to a function requiring a specific input type > > and most importantly: > > d) a user of the library should get a clear and simple error message > when using templates with parameters that do not fulfil the required > concept. Although the following code works acceptably with dmd 2.056, sometimes the error messages are less than ideal. This may happen when there are overloads of a function template and none of them accept a template parameter. The compiler can only say that "there is no function template for this use". > > Has anyone achieved these goals better than my feeble attempt? > > Greetings, > Norbert Here is something: template hasType(T) { enum bool hasType = is(T.type); } template hasNonNegativeLength(T) { enum bool hasNonNegativeLength = T.len >= 0; } template firstElementSameType(T) { enum bool firstElementSameType = is(typeof(T.init[0]) == T.type); } template matchesMyConcept(T) { enum bool matchesMyConcept = (hasType!T && hasNonNegativeLength!T && firstElementSameType!T); } struct MyClass(T,int R) { alias T type; enum len = R; T[R] value; T opIndex(int idx) { return value[idx]; } } struct YourClass {} void myfunction(A)(A arr) if (matchesMyConcept!A) { } unittest { MyClass!(int,4) x; myfunction(x); // <-- this works fine YourClass y; myfunction(y); // <-- compilation ERROR for this one } void main() {} Ali | |||
November 20, 2011 Re: Concepts vs template constraints - the practical approach | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Ali Çehreli | Hi Ali, indeed, defining individual named sub-concepts makes the thing somewhat more readable. However, my approach with individual static assertions was very intentional: Collecting individual requirements as an AND expression of booleans does not allow any helpful error message. If just one of the requirements is not met, there is just one failure of the global assertion. I had also toyed with boolean wrappers for the "__traits(compiles,...)" construct. It does work, but still it feels way more hacky than anything I would want to use at the foundation of a general library. Greetings, Norbert On 20.11.2011 19:47, Ali Çehreli wrote: > > On 11/20/2011 08:41 AM, Norbert Nemec wrote: > > Hi there, > > > > back in the discussions about C++-"concepts" it was argued that > > D-template-parameter constraints allow you to achieve the same goal. > > Have a look at std.range.hasLength, std.range.isInputRange, and friends. [...] > Here is something: > > template hasType(T) > { > enum bool hasType = is(T.type); > } > > template hasNonNegativeLength(T) > { > enum bool hasNonNegativeLength = T.len >= 0; > } > > template firstElementSameType(T) > { > enum bool firstElementSameType = is(typeof(T.init[0]) == T.type); > } > > template matchesMyConcept(T) > { > enum bool matchesMyConcept = (hasType!T && > hasNonNegativeLength!T && > firstElementSameType!T); > } | |||
November 20, 2011 Re: Concepts vs template constraints - the practical approach | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Denis Shelomovskij | On 20.11.2011 19:58, Denis Shelomovskij wrote:
>> void myfunction(A)(A arr)
>> if(__traits(compiles, verifyMyConcept!(A)))
>> {
>> }
>
> It should be
> ---
> void myfunction(A)(A arr)
> {
> verifyMyConcept!A;
> }
> ---
> to see the error messages.
Indeed - that way you win a meaningful error message but you loose the possibility for overloading. Guess, one really has to choose between the two... :-(
| |||
November 20, 2011 Re: Concepts vs template constraints - the practical approach | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Denis Shelomovskij | Nice! That really improves things! Looking forward to playing with it a little more...
On 20.11.2011 20:31, Denis Shelomovskij wrote:
> Your example, a bit improved:
> ---
> // In std.concept, e.g.
> template verify(alias concept, T) {
> //static assert(isConcept!concept, concept.stringof ~ " is not a
> concept"); //the test for particular concept's concepts can be added
> mixin concept!T;
> }
>
> template verify(alias concept) {
> mixin verify!(concept, typeof(this));
> }
>
> template satisfy(alias concept, T) {
> enum satisfy = __traits(compiles, verify!(concept, T));
> }
>
> // User code
> template myConcept(A) {
> static assert(is(A.type), "Error 1: !is(A.type)");
> static assert(A.len >= 0, "Error 2: A.len < 0");
> static assert(is(typeof(A.init[0]) == A.type), "Error 3:
> typeof(A.init[0]) != A.type");
> }
>
> struct MyClass(T,int R) {
> alias T type;
> enum len = R;
>
> T[R] value;
>
> T opIndex(int idx) {
> return value[idx];
> }
>
> mixin verify!myConcept;
> }
>
> void myFunction(A)(A arr) {
> mixin verify!(myConcept, A);
> }
>
> void myOverlodedFunction(A)(A arr) if(satisfy!(myConcept, A)) {
> // Do something
> }
>
> void myOverlodedFunction(A)(int i, A arr) if(satisfy!(myConcept, A)) {
> // Do something
> }
>
> void myOverlodedFunction(T...)(T) {
> static assert(0, "Error: here should be some user-defined error
> message(s) based on T");
> }
>
> unittest {
> MyClass!(int,4) x;
> myFunction(x);
> //myFunction(1); //Error: static assert "Error 1: !is(A.type)"
> myOverlodedFunction(x);
> myOverlodedFunction(3, x);
> //myOverlodedFunction(3, 2); //Error: static assert "Error: here should
> be..."
> }
> ---
| |||
November 20, 2011 Re: Concepts vs template constraints - the practical approach | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Norbert Nemec | On 11/20/2011 11:36 AM, Norbert Nemec wrote: > Hi Ali, > > indeed, defining individual named sub-concepts makes the thing somewhat > more readable. > > However, my approach with individual static assertions was very > intentional: > > Collecting individual requirements as an AND expression of booleans does > not allow any helpful error message. If just one of the requirements is > not met, there is just one failure of the global assertion. dmd 2.056 with the code that I've tried, does provide which individual requirement is not met: deneme.d(54697): Error: no property 'len' for type 'YourClass' deneme.d(54709): Error: template instance deneme.hasNonNegativeLength!(YourClass) error instantiating deneme.d(54727): instantiated from here: matchesMyConcept!(YourClass) > I had also toyed with boolean wrappers for the "__traits(compiles,...)" > construct. It does work, but still it feels way more hacky than anything > I would want to use at the foundation of a general library. Agreed. > > Greetings, > Norbert Ali > > > > On 20.11.2011 19:47, Ali Çehreli wrote: >> >> On 11/20/2011 08:41 AM, Norbert Nemec wrote: >> > Hi there, >> > >> > back in the discussions about C++-"concepts" it was argued that >> > D-template-parameter constraints allow you to achieve the same goal. >> >> Have a look at std.range.hasLength, std.range.isInputRange, and friends. > [...] >> Here is something: >> >> template hasType(T) >> { >> enum bool hasType = is(T.type); >> } >> >> template hasNonNegativeLength(T) >> { >> enum bool hasNonNegativeLength = T.len >= 0; >> } >> >> template firstElementSameType(T) >> { >> enum bool firstElementSameType = is(typeof(T.init[0]) == T.type); >> } >> >> template matchesMyConcept(T) >> { >> enum bool matchesMyConcept = (hasType!T && >> hasNonNegativeLength!T && >> firstElementSameType!T); >> } > | |||
November 20, 2011 Re: Concepts vs template constraints - the practical approach | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Norbert Nemec | On 11/20/11 10:41 AM, Norbert Nemec wrote: [snip] Why not follow the patter of isXxx in the standard library? Andrei | |||
November 21, 2011 Re: Concepts vs template constraints - the practical approach | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Ali Çehreli | On 20.11.2011 21:03, Ali Çehreli wrote:
> On 11/20/2011 11:36 AM, Norbert Nemec wrote:
>> Collecting individual requirements as an AND expression of booleans does
>> not allow any helpful error message. If just one of the requirements is
>> not met, there is just one failure of the global assertion.
>
> dmd 2.056 with the code that I've tried, does provide which individual
> requirement is not met:
>
> deneme.d(54697): Error: no property 'len' for type 'YourClass'
> deneme.d(54709): Error: template instance
> deneme.hasNonNegativeLength!(YourClass) error instantiating
> deneme.d(54727): instantiated from here: matchesMyConcept!(YourClass)
True, there is some additional detail in some cases, but if you look closely: dmd does not complain about requirements that compute as "false", but about subexpressions that fail to compile at all.
In fact, such an error even means that the check is unusable as constraint: A template constraint should always compile without error and simply return true or false.
| |||
Copyright © 1999-2021 by the D Language Foundation
Permalink
Reply