concepts and incomplete types

99 views
Skip to first unread message

Sergey Strukov

unread,
May 23, 2016, 5:48:23 AM5/23/16
to SG8 - Concepts
Based on my experience the main problem with the current concepts is the working with incomplete types. It makes difficult to use requirements with CRTP, for example.
For instance, in the following code I cannot up requirements to the class template itself.

template <class T> // requires ( Has_objCmp<T> )
struct CmpComparable
 
{
 
friend bool operator < (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)<0; }


 
friend bool operator > (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)>0; }


 
friend bool operator <= (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)<=0; }


 
friend bool operator >= (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)>=0; }


 
friend bool operator == (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)==0; }


 
friend bool operator != (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)!=0; }
 
};


 Because in the intended usage it would fail.

class SomeClass : public CmpComparable<SomeClass> // incomplete type here
 
{
 
....
 
};


This problem cannot be solved by deferring the requirement check to a later phase of translation (for example, for the moment the type becomes complete),
because in other situations, when the requirements are used to select implementation they must be checked immediately.

Matt Calabrese

unread,
May 23, 2016, 6:54:09 PM5/23/16
to conc...@isocpp.org
I don't speak for Concepts folks, but I personally do not see this as a defect. What exactly do you expect to happen here? You cannot check the requirement at that point because the child type would be incomplete and you'd need the type to be complete in order for the check to be valid. IMO, this is more just a symptom of CRTP being a hack (albeit a very useful hack). How do you suggest this should work if this is considered a defect?

FWIW, I have a very similar CRTP base to what you have here in my own code-base and I effectively constrain it the way you do in the example (though currently in C++11 without concepts, and based on operator< ala Boost.Operators instead of a separate comparison function). I'd personally argue that it's more useful to constrain as you are already doing now, since it means that a child template can unconditionally inherit from the CRTP base and effectively provide the operators if and only if the child supplies the required operation. For instance, something like std::vector's comparison operators are only valid to invoke if the associated value_type provides operator<. This conditional support can be easily provided and be properly constrained using something akin to your current definition. If, instead, the constraints were placed on the entire CmpComparable template, then you would be unable to directly use that base in the default definition of your std::vector-like type and instead you'd have to conditionally branch using metaprogramming in the child template definition that inherits from the CRTP base. This situation isn't particularly unique to a std::vector-like type, either, as it's a common case in a lot of generic code to conditionally provide some Regular functionality based on whether or not certain field types meet the necessary requirements.

Andrew Sutton

unread,
May 23, 2016, 11:16:25 PM5/23/16
to conc...@isocpp.org
On Mon, May 23, 2016 at 6:54 PM Matt Calabrese <metaprogram...@gmail.com> wrote:
On Mon, May 23, 2016 at 2:48 AM, Sergey Strukov <sshi...@hotmail.com> wrote:
Based on my experience the main problem with the current concepts is the working with incomplete types. It makes difficult to use requirements with CRTP, for example.
For instance, in the following code I cannot up requirements to the class template itself.

template <class T> // requires ( Has_objCmp<T> )
struct CmpComparable
 
{
 
friend bool operator < (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)<0; }


 
friend bool operator > (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)>0; }


 
friend bool operator <= (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)<=0; }


 
friend bool operator >= (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)>=0; }


 
friend bool operator == (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)==0; }


 
friend bool operator != (const T &a,const T &b) requires ( Has_objCmp<T> ) { return a.objCmp(b)!=0; }
 
};


 Because in the intended usage it would fail.

class SomeClass : public CmpComparable<SomeClass> // incomplete type here
 
{
 
....
 
};


This problem cannot be solved by deferring the requirement check to a later phase of translation (for example, for the moment the type becomes complete),
because in other situations, when the requirements are used to select implementation they must be checked immediately.

I don't speak for Concepts folks, but I personally do not see this as a defect.

It's not a defect. These are just the normal lookup rules being applied at the point of use. 
 
Andrew
--
Andrew Sutton

Bjarne Stroustrup

unread,
May 24, 2016, 2:31:10 AM5/24/16
to conc...@isocpp.org

One purpose of concepts is to get early checking. The purpose of CRCP is to delay checking. I don't see how you could have both.

--
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+u...@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/.

Ville Voutilainen

unread,
May 24, 2016, 2:36:25 AM5/24/16
to conc...@isocpp.org
On 24 May 2016 at 09:31, Bjarne Stroustrup <bja...@stroustrup.com> wrote:
> One purpose of concepts is to get early checking. The purpose of CRCP is to
> delay checking. I don't see how you could have both.


Another remark: Concepts don't introduce this issue, although they
don't strive to magically solve it either.
We're talking about a case where one seemingly wants to constrain a
template parameter that is known
to be of an incomplete type. I can't fathom any other way around it
than postponing the constraints
to a point where the aforementioned type is complete.

Sergey Strukov

unread,
May 24, 2016, 7:16:29 AM5/24/16
to SG8 - Concepts


On Tuesday, May 24, 2016 at 9:31:10 AM UTC+3, Bjarne Stroustrup wrote:

One purpose of concepts is to get early checking. The purpose of CRCP is to delay checking. I don't see how you could have both.



Well, technically you can have both. You can defer the concept check for incomplete types to the point where the type becomes complete. In the example above its after SomeClass declaration. 
You will have the same diagnostic in case concept check failure.
But if a requirement is used to select an implementation (for example, there is a set of overloaded functions or specialized classes), it should be checked immediately.
The only problem is the implementation difficulty for the compiler developers.

And I don't think "The purpose of CRTP is to delay checking". This pattern gives a mechanism for implementation injection, not for delay checking. C++ does not have partial classes so far.

Matt Calabrese

unread,
May 24, 2016, 7:16:54 PM5/24/16
to conc...@isocpp.org
On Mon, May 23, 2016 at 11:36 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 24 May 2016 at 09:31, Bjarne Stroustrup <bja...@stroustrup.com> wrote:
> One purpose of concepts is to get early checking. The purpose of CRCP is to
> delay checking. I don't see how you could have both.


Another remark: Concepts don't introduce this issue, although they
don't strive to magically solve it either.

+1
 
On Mon, May 23, 2016 at 11:36 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
We're talking about a case where one seemingly wants to constrain a
template parameter that is known
to be of an incomplete type. I can't fathom any other way around it
than postponing the constraints
to a point where the aforementioned type is complete.

Also, it's perfectly fine to have a concept that doesn't require complete types, such as a SameType concept. Certainly a user wouldn't want to "delay" the check there until the type is complete -- is the implementation expected to know which concepts are safe to be checked with incomplete types and which are not? In addition to that, some incomplete types may not be completed in the current translation unit (or ever at all, for that matter).

IMO, trying to get concepts to be able to constrain the incomplete child type of a CRTP base in ways that require the child type to be complete are simply misuses. As for an alternative if you still really want to constrain the CRTP base at that point, you can define a trait specialization prior to the child type's definition. The trait would contain the compare function declaration, and you can, of course, instantiate the trait with an incomplete type. Your CRTP base could then be constrained based on the existence of such a comparison function in the trait rather than introspecting the child type directly. This allows the check to occur immediately even though the child type is incomplete at that point.

That said, I personally would just recommend doing what you're doing now. I only present the alternative as a way to get immediate checking if you really want it.
Reply all
Reply to author
Forward
0 new messages