Topic: Concepts: Why can't it be don't following duck typing


Author: "Andrei Polushin" <polushin@gmail.com>
Date: Mon, 20 Nov 2006 00:26:17 CST
Raw View
Andrei Polushin wrote:
>     template <> class vector<bool> : public BasicVector {
>         void push_back(T x) { ... }
>     };

Correction: push_back(bool x) { ... }

> The _first_ difference is that we should declare the concept map
> externally, literally repeating the definitions of the primary concept,
> and I doubt it's worth enough. So I think it doesn't "prevent us",
> but rather it doesn't encourage our use of possible technique:
>
>     concept Vector<T> {
>         void push_back(T x);

Correction to match N2081 syntax:

      concept Vector<class self, typename T> {
          void self::push_back(T x);
>         // + 20 constraining methods with useless default definitions
>     };
>
>     template <class T>
>     class vector {
>         void push_back(T x);
>         // + the same 20 methods, default definitions ignored
>     };
>
>     template <class T>
>     concept_map Vector<vector<T> > {
>         void push_back(T x);

Correction to match N2081 syntax:

          void vector<T>::push_back(T x);
>         // + the same 20 methods, copy-pasted from Vector<T>
>     };
>
> That's almost it, except that we casually define the concept_map for
> vector<bool> too, and it's the _second_ difference: we didn't want it.

And the _third_ most crucial difference: you didn't allow us to write
functions that operate on pure concepts:

    concept Vector<class self, typename T> { // N2081 syntax
        void self::push_back(T x);
    };

    template <CopyConstructible T>
    void foo(Vector<T>& vec) {              // not allowed
        // ...
    }

That's because your concepts are not the types, I guess.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Mon, 20 Nov 2006 10:20:17 CST
Raw View
Andrei Polushin wrote:
> And the _third_ most crucial difference: you didn't allow us to write
> functions that operate on pure concepts:
>
>     concept Vector<class self, typename T> { // N2081 syntax
>         void self::push_back(T x);
>     };
>
>     template <CopyConstructible T>
>     void foo(Vector<T>& vec) {              // not allowed
>         // ...
>     }

Well, one can use any type "V" that meets the requirements of the
Vector concept:

  template<typename V>
  where Vector<V>
  void foo(V& vec) {
    Vector<V>::value_type& x = vec.front();
  }

I don't think this particular difference has anything to do with
separately compilation.

> That's because your concepts are not the types, I guess.

Right.

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Mon, 20 Nov 2006 11:14:21 CST
Raw View
Andrei Polushin wrote:
> Douglas Gregor wrote:
> > That effectively doubles the compilation time for "foo". [...]
>
> Well, that was the wrong way. Trying again.
[snip example]
> Alas, vector<bool> does not derive from vector<T>, so it doesn't match
> the separately compiled version of generic foo(). There are several
> possible alternatives left for the author of foo():
>
>     1. Provide the implementation of foo() in source form,
>        not separately compiled (very bad);
>
>     2. Provide the specialization foo<bool>() for vector<bool>,
>        separately compiled (still bad);
>
>     3. Make the foo more constrained, declare it as foo(Vector<T>&),
>        so it can be compiled separately.
>
> The last version of foo operates on concept Vector<T>, so we can pass
> any class that derives from Vector<T>.

Yes, and all of this can be done in the current concepts system as
well, although the details differ when you move away from the OO view.
The compilation strategies are somewhat different, because one does not
have virtual function tables for concepts, although there is an
analagous notion of a "dictionary" that gets passed around (separately
from the objects... but implementation details don't matter so much
now).

Still, what does that tell us about our original "foo", which used
plain 'ol vector<T>:

  template<CopyConstructible T>
  void foo(vector<T>& vec) {
    T& x = vec.front();
  }

Is this code legal if there exists a vector<bool> specialization?
  - If so, does calling foo with a vector<bool>& use that
specialization?
    + If so, we have a problem for separate compilation: we've
introduced run-time errors (!)
    + If not, we've changed the semantics of C++ significantly, because
specializations aren't used
  - If not, we're saying that one can't use a class template inside a
concept-constrained template (one instead would need to use, e.g., the
Vector concept. That's a lot of work for the user?)

Is there another way out? We're not sure. We can't restrict what
specializations can do (because that would break all metaprogramming
constructs, among other things) and we can't just ban the use of class
templates inside constrained templates (it would make code extremely
verbose). We wrestled with this a long time: being able to guarantee
completely modular type checking, and potentially enable separate
compilation, is a worthy goal... but the cost to existing C++ code
bases seems very high. We can't factor every class template "vector"
into Vector and BasicVector concepts, then change all of our generic
uses of vector<T> to use the concepts; it's infeasible. So, we need
some kind of middle ground.

> The _first_ difference is that we should declare the concept map
> externally, literally repeating the definitions of the primary concept,
> and I doubt it's worth enough. So I think it doesn't "prevent us",
> but rather it doesn't encourage our use of possible technique:
>
>     concept Vector<T> {
>         void push_back(T x);
>         // + 20 constraining methods with useless default definitions
>     };
>
>     template <class T
>     class vector {
>         void push_back(T x);
>         // + the same 20 methods, default definitions ignored
>     };
>
>     template <class T>
>     concept_map Vector<vector<T> > {
>         void push_back(T x);
>         // + the same 20 methods, copy-pasted from Vector<T>
>     };

Of course, if push_back and those other 20 methods have declarations in
vector<T> that match the signatures in the Vector concept, you can just
leave the concept map with an empty definition:

  template<class T> concept_map Vector<vector<T> > { }

You only need to provide definitions in a concept map when you are
remapping syntax.

> That's almost it, except that we casually define the concept_map for
> vector<bool> too, and it's the _second_ difference: we didn't want it.
> I want to bind the concept Vector<T> and non-specialized vector<T>,
> but not yet any specialization of vector<T>.

If your Vector concept has some requirement that vector<bool> doesn't
meet, e.g.,

  concept Vector<typename X> {
    typename value_type = X::value_type;
    value_type& X::front();
  }

and you write a concept map like this:

    concept_map Vector<vector<bool> > { }

the compiler will reject the concept map, because vector<bool>::front
returns something that is not convertible to bool&.

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Mon, 20 Nov 2006 13:43:44 CST
Raw View
From: xxx

Douglas Gregor wrote:
> Still, what does that tell us about our original "foo", which used
> plain 'ol vector<T>:
>
>   template<CopyConstructible T>
>   void foo(vector<T>& vec) {
>     T& x = vec.front();
>   }
>
> Is this code legal if there exists a vector<bool> specialization?

It must be legal for compatibility.


>   - If so, does calling foo with a vector<bool>& use that
> specialization?

Yes, for compatibility. No, it cannot be separately compiled.


>     + If so, we have a problem for separate compilation: we've
> introduced run-time errors (!)

We should compile foo() only for primary vector<T>.


>     + If not, we've changed the semantics of C++ significantly, because
> specializations aren't used

The specializations are used, but we don't have a version of foo()
precompiled for a specialization of vector. User should find the source
code of foo(), or find the compiler that implements "export". Just like
he does it today.


>   - If not, we're saying that one can't use a class template inside a
> concept-constrained template (one instead would need to use, e.g., the
> Vector concept. That's a lot of work for the user?)

There should be a less work, because it should be convenient :)


> Is there another way out? We're not sure. We can't restrict what
> specializations can do (because that would break all metaprogramming
> constructs, among other things) and we can't just ban the use of class
> templates inside constrained templates (it would make code extremely
> verbose). We wrestled with this a long time: being able to guarantee
> completely modular type checking, and potentially enable separate
> compilation, is a worthy goal... but the cost to existing C++ code
> bases seems very high. We can't factor every class template "vector"
> into Vector and BasicVector concepts, then change all of our generic
> uses of vector<T> to use the concepts; it's infeasible. So, we need
> some kind of middle ground.

Refactoring the uses of vector<T> into uses of Vector<T> is better
than the inline templates we currently have, so the users will find
it reasonable to refactor.

Another idea is to relax the byte code linkage rules:

  - when compiler sees the foo(vector<T>&) that actually uses only
    BasicVector<T> members, it should produce the byte code with the
    signature foo(BasicVector<T>&).

  - the byte code linker looks for foo(BasicVector<T>&), then for
    foo(Vector<T>& ), then for foo(vector<T>& ), in that order.
    If it finds the foo(BasicVector<T>& ) then it can be used with
    vector<bool>, otherwise the byte code linkage error occurs.

  - in case of multiple independent concepts, there could be an
    linkage ambiguity, which should count as an error.

But we can't avoid manual extraction of the concepts Vector<T> and
BasicVector<T> from template vector<T>. User should do it himself.


> Of course, if push_back and those other 20 methods have declarations in
> vector<T> that match the signatures in the Vector concept, you can just
> leave the concept map with an empty definition:
>
>   template<class T> concept_map Vector<vector<T> > { }
>
> You only need to provide definitions in a concept map when you are
> remapping syntax.

Good.


> If your Vector concept has some requirement that vector<bool> doesn't
> meet, [...] and you write a concept map like this: [...]
> the compiler will reject the concept map, because vector<bool>::front
> returns something that is not convertible to bool&.

It's like the "auto concept" that applies to all specializations. I
guess it cannot be error-prone here, but will generate a confusing
error message.

Auto concepts are the source of homonymy mistakes otherwise.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Sat, 11 Nov 2006 01:57:04 GMT
Raw View
"Andrei Polushin" <polushin@gmail.com> writes:

> David Abrahams wrote:
>> >
>> >
>> > This only works for "dependent" multi-type concepts.  What you can't
>> > do is write a matrix-vector multiplication function that requires that
>> > together, the matrix and vector satisfy the VectorSpace<M,V> concept.
>> >
>> >   template <class M, class V>
>> >   where VectorSpace<M,V>
>> >   V mul(M m, V v)
>> >   {
>> >       ...
>> >   }
>
> Why can't I write it your way?:
>
>     template <typename M, typename V>
>        where <VectorSpace<M,V> S>
>     V mul(M m, V v)
>     {
>         ...
>     }

Well, that's not "my way;" that's just your minor variation of what's
in the proposal... and you _could_ write it that way, provided that
allowing that syntax doesn't break something else of importance.  It
might even be a good idea, as an extension to the current proposal (in
fact, I'm not sure the current proposal doesn't allow that syntax;
it's been a while since I read it).  But now you're a lot closer to
the existing proposal than you were before (or maybe you're
duplicating it).

Look, the latest concepts proposal isn't something Doug whipped up by
himself one weekend; its the product of years of thinking on the parts
of many people, including Bjarne Stroustrup.  I think it would be
great for everybody if you could come up with something better, but if
you're adjusting your proposal on a daily basis to accomodate the
problems that are pointed out, it's a hard to take your ideas very
seriously.  Your posts seem an awful lot like casual "armchair
quarterbacking" to me.

If you're really sure there's a better way, sit down and do the hard
work.  Look at the use cases, and look at the complete history of
papers on this topic to see what's already been tried and rejected,
and write a paper that shows us the way.

> If you'll see it as many-to-many relationship between M and V, then
> the intermediate entity S would be appropriate.

Yes, that might be a good idea, since you now have a concise way to
name the associated types.

> Other way is to extract a common entity from both M and V, then check
> it for compatibility:
>
>     template <PartOfSpace M, PartOfSpace V>
>        where <CompatibleSpace<M::vector_space> V::vector_space>
>     V mul(M m, V v)
>     {
>         ...
>     }
>
> This is exactly what we do to interrelate InputIterator to
> OutputIterator using their "reference" types.

Well, no, we don't do that (the reference type of an output iterator
is very commonly void), but I think I know what you mean anyway.  It
doesn't work, though.  In the case of iterators, the reference type is
an associated type of a single-type concept.  Here there is no one
vector space associated with a given matrix or vector.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Sat, 11 Nov 2006 00:47:46 CST
Raw View
David Abrahams wrote:
> "Andrei Polushin" writes:
>> David Abrahams wrote:
>>>> This only works for "dependent" multi-type concepts.  What you can't
>>>> do is write a matrix-vector multiplication function that requires that
>>>> together, the matrix and vector satisfy the VectorSpace<M,V> concept.
>>>>
>>>>   template <class M, class V>
>>>>   where VectorSpace<M,V>
>>>>   V mul(M m, V v)
>>>>   {
>>>>       ...
>>>>   }
>> Why can't I write it your way?:
>>
>>     template <typename M, typename V>
>>        where <VectorSpace<M,V> S>
>>     V mul(M m, V v)
>>     {
>>         ...
>>     }
>
> Well, that's not "my way;" that's just your minor variation of what's
> in the proposal... and you _could_ write it that way, provided that
> allowing that syntax doesn't break something else of importance.

Well, I apologize for "your way", but I'm not a native English speaker,
so I am not aware of all subtleties in my phrases.


> It
> might even be a good idea, as an extension to the current proposal (in
> fact, I'm not sure the current proposal doesn't allow that syntax;
> it's been a while since I read it).  But now you're a lot closer to
> the existing proposal than you were before (or maybe you're
> duplicating it).

I do not propose something. I was answering to your assertion above,
just showing that it is possible to express multi-type constraints with
the concepts constraining one type at once. My conclusions were drawn
from the "type-of-a-type" view and from my previous experience with the
OO programming: *if it was possible there, it should be possible here*.
Thus I do not invent something completely new with concepts, and I see
no need to re-invent them from scratch.


> Look, the latest concepts proposal isn't something Doug whipped up by
> himself one weekend; its the product of years of thinking on the parts
> of many people, including Bjarne Stroustrup.  I think it would be
> great for everybody if you could come up with something better, but if
> you're adjusting your proposal on a daily basis to accomodate the
> problems that are pointed out, it's a hard to take your ideas very
> seriously.  Your posts seem an awful lot like casual "armchair
> quarterbacking" to me.
>
> If you're really sure there's a better way, sit down and do the hard
> work.  Look at the use cases, and look at the complete history of
> papers on this topic to see what's already been tried and rejected,
> and write a paper that shows us the way.

Looks like a personal attack on me.

I appreciate the work of Doug, Bjarne, you, and many others, but...
Their work shouldn't be broken by several simple questions "why?".
Or there should be some answers. Shooting me is not the answer :)

Doug answers to most of my questions, and they are not only my
questions, but the possible questions of many others, who didn't
implement concept themselves. I hope this discussion will raise the
interest to the concepts, so the number of questions will multiply.
And you cannot just answer with the name "Bjarne" to all of them :)


>> If you'll see it as many-to-many relationship between M and V, then
>> the intermediate entity S would be appropriate.
>
> Yes, that might be a good idea, since you now have a concise way to
> name the associated types.
>
>> Other way is to extract a common entity from both M and V, then check
>> it for compatibility:
>>
>>     template <PartOfSpace M, PartOfSpace V>
>>        where <CompatibleSpace<M::vector_space> V::vector_space>
>>     V mul(M m, V v)
>>     {
>>         ...
>>     }
>>
>> This is exactly what we do to interrelate InputIterator to
>> OutputIterator using their "reference" types.
>
> Well, no, we don't do that (the reference type of an output iterator
> is very commonly void), but I think I know what you mean anyway.  It
> doesn't work, though.  In the case of iterators, the reference type is
> an associated type of a single-type concept.  Here there is no one
> vector space associated with a given matrix or vector.

I think the problem is more closely related to the problem with
multimethods (D&E 13.8). It has a possible solution, known as "double
dispatching", and I expect it is applicable to this case.

Disclaimer: I cannot prove it right now :)


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: David Abrahams <dave@boost-consulting.com>
Date: Sat, 11 Nov 2006 04:19:43 CST
Raw View
"Andrei Polushin" <polushin@gmail.com> writes:

> David Abrahams wrote:
>> "Andrei Polushin" writes:
>>> David Abrahams wrote:
>>>>> This only works for "dependent" multi-type concepts.  What you can't
>>>>> do is write a matrix-vector multiplication function that requires that
>>>>> together, the matrix and vector satisfy the VectorSpace<M,V> concept.
>>>>>
>>>>>   template <class M, class V>
>>>>>   where VectorSpace<M,V>
>>>>>   V mul(M m, V v)
>>>>>   {
>>>>>       ...
>>>>>   }
>>> Why can't I write it your way?:
>>>
>>>     template <typename M, typename V>
>>>        where <VectorSpace<M,V> S>
>>>     V mul(M m, V v)
>>>     {
>>>         ...
>>>     }
>>
>> Well, that's not "my way;" that's just your minor variation of what's
>> in the proposal... and you _could_ write it that way, provided that
>> allowing that syntax doesn't break something else of importance.
>
> Well, I apologize for "your way", but I'm not a native English speaker,
> so I am not aware of all subtleties in my phrases.

No need to apologize; I wasn't offended.

>> It might even be a good idea, as an extension to the current
>> proposal (in fact, I'm not sure the current proposal doesn't allow
>> that syntax; it's been a while since I read it).  But now you're a
>> lot closer to the existing proposal than you were before (or maybe
>> you're duplicating it).
>
> I do not propose something. I was answering to your assertion above,
> just showing that it is possible to express multi-type constraints with
> the concepts constraining one type at once.

I don't see any example of a concept constraining only a single type
above.

> My conclusions were drawn from the "type-of-a-type" view and from my
> previous experience with the OO programming: *if it was possible
> there, it should be possible here*.

Yeah, well generic programming and OO programming are very different.
OO programming is especially weak when it comes to covariant argument
types (see "the binary method problem"), whereas generic programming
is not.  It's no coincidence that the cases your approach has trouble
with are the ones with covariant template arguments.  You can impose
the "type of a type" view on generic programming, but it will end up
weakening its ability to handle those cases.

> Thus I do not invent something completely new with concepts, and I
> see no need to re-invent them from scratch.

Nobody is inventing them from scratch; the proposal is grounded in the
earliest work with concepts by Stepanov and Musser.  It's
fundamentally different from OO, so if OO is your approach to
everything, it's no surprise that it doesn't fit well into your mental
model.

>> Look, the latest concepts proposal isn't something Doug whipped up by
>> himself one weekend; its the product of years of thinking on the parts
>> of many people, including Bjarne Stroustrup.  I think it would be
>> great for everybody if you could come up with something better, but if
>> you're adjusting your proposal on a daily basis to accomodate the
>> problems that are pointed out, it's a hard to take your ideas very
>> seriously.  Your posts seem an awful lot like casual "armchair
>> quarterbacking" to me.
>>
>> If you're really sure there's a better way, sit down and do the hard
>> work.  Look at the use cases, and look at the complete history of
>> papers on this topic to see what's already been tried and rejected,
>> and write a paper that shows us the way.
>
> Looks like a personal attack on me.

Not at all; it's merely a challenge to bring some rigor to your
inquiry.

> Doug answers to most of my questions,

As have I.

> and they are not only my questions, but the possible questions of
> many others, who didn't implement concept themselves. I hope this
> discussion will raise the interest to the concepts, so the number of
> questions will multiply.  And you cannot just answer with the name
> "Bjarne" to all of them :)

Have I really done that with all of your questions?

Listen, if you start out with the premise that generic programming
should really look like OO programming, and you won't be satisfied
until it fits into that model, you'll never be happy.  They're
just different.

>>> Other way is to extract a common entity from both M and V, then check
>>> it for compatibility:
>>>
>>>     template <PartOfSpace M, PartOfSpace V>
>>>        where <CompatibleSpace<M::vector_space> V::vector_space>
>>>     V mul(M m, V v)
>>>     {
>>>         ...
>>>     }
>>>
>>> This is exactly what we do to interrelate InputIterator to
>>> OutputIterator using their "reference" types.
>>
>> Well, no, we don't do that (the reference type of an output iterator
>> is very commonly void), but I think I know what you mean anyway.  It
>> doesn't work, though.  In the case of iterators, the reference type is
>> an associated type of a single-type concept.  Here there is no one
>> vector space associated with a given matrix or vector.
>
> I think the problem is more closely related to the problem with
> multimethods (D&E 13.8).

More closely than what?

My assertion that there is no one vector space associated with a given
matrix or vector is fundamental; there's no need to go chasing
multimethods to find out why the above doesn't work.

> It has a possible solution, known as "double
> dispatching", and I expect it is applicable to this case.
>
> Disclaimer: I cannot prove it right now :)

Yes, double dispatching addresses the binary method problem in a
sense, but you give up static type safety, and, of course,
performance.  Both these sacrifices are incompatible with the goals of
generic programming.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: Sat, 11 Nov 2006 16:56:41 CST
Raw View
In article <871woafip5.fsf@pereiro.luannocracy.com>, David Abrahams
<dave@boost-consulting.com> writes
>Listen, if you start out with the premise that generic programming
>should really look like OO programming, and you won't be satisfied
>until it fits into that model, you'll never be happy.  They're
>just different.

About 12 years ago I was standing in a queue (line) waiting to go into a
talk at a conference and in front of me were Andy Koenig and Jim
Coplien. I commented that I thought I had at last got OO sorted in my
mind. Jim smiled and remarked 'meanwhile the rest of us have moved on.'
(No there was negative content, and I was perfectly happy with the
comment)

Why do I mention this? Well it is very easy to view something new by
forcing it into an earlier view. It really does require a mental shift
to go from procedural programming to OO programming and one that we do
not recognise till we have made it. In the same way it requires a mental
shift to view generic programming in its own right but until we have
made it we do not recognise it.

The fully rounded programmer moves from one view to another as is
desirable for the work at hand. They do not allow a single view to
constrain their work. However to do this you need (at least implicitly)
to understand that there are many views and choose the one helpful for
now. And remember that the area being dealt with now includes yet
another shift (meta-programming)



--
Francis Glassborow      ACCU
Author of 'You Can Do It!' and "You Can Program in C++"
see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Mon, 13 Nov 2006 10:55:05 CST
Raw View
Francis Glassborow wrote:
> David Abrahams writes
>> Listen, if you start out with the premise that generic programming
>> should really look like OO programming, and you won't be satisfied
>> until it fits into that model, you'll never be happy.  They're
>> just different.
>
> About 12 years ago I was standing in a queue (line) waiting to go into a
> talk at a conference and in front of me were Andy Koenig and Jim
> Coplien. I commented that I thought I had at last got OO sorted in my
> mind. Jim smiled and remarked 'meanwhile the rest of us have moved on.'
> (No there was negative content, and I was perfectly happy with the comment)
>
> Why do I mention this? Well it is very easy to view something new by
> forcing it into an earlier view.

I would say it's the only possible approach to start with something.

> It really does require a mental shift
> to go from procedural programming to OO programming and one that we do
> not recognise till we have made it. In the same way it requires a mental
> shift to view generic programming in its own right but until we have
> made it we do not recognise it.
>
> The fully rounded programmer moves from one view to another as is
> desirable for the work at hand.

Exactly. I was looking for something desirable for extracting the nail
from my armchair. And found a space shuttle, suitable to disperse the
clouds in the heaven. No, there is negative content; it could be used
for many useful tasks. Now I should align my expectations for that, and
find a good nail drawer instead.

So I should find a nail drawer if I need:

    - the syntax I'm familiar with;
    - true separate compilation;
    - something to replace CRTP in my code;
    - something to mix requirements' implementation into my code.

But I will need a space shuttle to get

    - better error messages from STL;
    - multi-type constraints, whatever they would be necessary;
    - the generic programming, whatever would it mean.

> They do not allow a single view to
> constrain their work. However to do this you need (at least implicitly)
> to understand that there are many views and choose the one helpful for
> now. And remember that the area being dealt with now includes yet
> another shift (meta-programming)

And what is it for you? Really, it's all about that: I would like to
know how others plan to use it.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Fri, 17 Nov 2006 22:44:35 CST
Raw View
Andrei Polushin wrote:
> In particular, with your example
>
> >   template<CopyConstructible T>
> >   void foo(vector<T>& vec) {
> >     T& x = vec.front();
> >   }
>
> I would prefer to have two separately compiled versions of foo, one
> for the primary template of vector, and another for the vector<bool>,
> because they are the only specializations foo sees at the point of
> its definition.

That effectively doubles the compilation time for "foo". Now if "foo"
had another template parameter "U", and used bar<U> in its definition
(where "bar" is another class template, that itself has a
specialization), you would need to compile "foo" four times to consider
all combinations. In the worst case, you use N different templates in
"foo", the ith of which has S(i) specializations, so you would need to
compile "foo" S(1) * S(2) * ... * S(N) times. That's going to have a
significant impact on compile times. So, the choice here was to
separately compile templates, but the cost of that separate compilation
increases very quickly with the number of specializations that might be
used.

> The second definition should fail to compile, so the
> author of foo should provide (or at least forward declare) a suitable
> overload. Then if anybody creates specialization for vector<int>, he
> will not have the definition of foo, because there was no vector<int>
> when foo gets compiled.

"export" has the same restriction, and it might be a reasonable one for
concepts. I have to think about it a bit more.

> It differs from "export", but it is a separate compilation, while the
> "export" is not.

Concepts do not provide separate compilation, because
concept-constrained templates are still instantiated just like
unconstrained templates. You need template instantiation to ensure that
constrained templates provide the same performance as unconstrained
templates. For true separate compilation (i.e., you can create a call
to a function template without having any knowledge of its definition,
anyhere), you must give up instantiation. That's a non-starter for many
reasons, especially performance: if adding concept constraints to your
template library makes it 40 times slower (which is not at all
unreasonable), you won't add concept constraints.

I imagine you are thinking of something akin to "export", where you can
separately "compile" definitions from their uses, but where the
compiler must load in that definition somehow for instantiation
purposes, e.g., at link time. The reason export failed to deliver
improved compile-time performance is because the compiler must load in
a huge amount of information to instantiate a template. Concepts might
reduce the amount of information that needs to be brought in, so that
an exported, concept-constrained template could provide some
benefits... but this is all speculative.

> What I'm afraid is that we will have the concepts that will still
> require possible yet incompatible redesign to support the separate
> compilation. The possibility could be discovered and proven many years
> later, but it will be too late to redesign them. Hence the risk.

Fair enough. If you are really concerned about this, the best thing you
can do is to determine what design decisions prevent us from providing
"separate compilation" (however you want to define it) with concepts.

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Sat, 18 Nov 2006 12:39:10 CST
Raw View
Douglas Gregor wrote:
> Andrei Polushin wrote:
>> In particular, with your example
>>
>>>   template<CopyConstructible T>
>>>   void foo(vector<T>& vec) {
>>>     T& x = vec.front();
>>>   }
>> I would prefer to have two separately compiled versions of foo, one
>> for the primary template of vector, and another for the vector<bool>,
>> because they are the only specializations foo sees at the point of
>> its definition.
>
> That effectively doubles the compilation time for "foo". [...]

Well, that was the wrong way. Trying again.

Consider the case without templates:

    // constraining interface
    class IVector {
        virtual void push_back(object x) = 0;
    };

    // class constrained by interface IVector
    class vector : public IVector {
        void push_back(object x) { ... }
    };

    void foo(IVector& vec, object x) {
        vec.push_back(x);
    }

The foo can be separately compiled, and used with any class that
implements IVector, would it be vector or something else.

How could it look like with constrained templates:

    // constraining concept
    template <class self, typename T>
    concept Vector {
        void push_back(T x);
    };

    // template class constrained by concept Vector
    template <class T>
    class vector : public Vector<T> {
        void push_back(T x) { ... }
    };

    template <class T>
    void foo(Vector<T>& vec, T x) {
        vec.push_back(x);
    }

Now the foo can be separately compiled, and used with any class that
constrained by concept Vector, namely the non-specialized vector<T>.

Now the legacy function

    template <class T>
    void foo(vector<T>& vec, T x) {
        vec.push_back(x);
    }

could be separately compiled for primary vector<T>, and used with any
class derived from (="constrained by") non-specialized vector<T>.

Alas, vector<bool> does not derive from vector<T>, so it doesn't match
the separately compiled version of generic foo(). There are several
possible alternatives left for the author of foo():

    1. Provide the implementation of foo() in source form,
       not separately compiled (very bad);

    2. Provide the specialization foo<bool>() for vector<bool>,
       separately compiled (still bad);

    3. Make the foo more constrained, declare it as foo(Vector<T>&),
       so it can be compiled separately.

The last version of foo operates on concept Vector<T>, so we can pass
any class that derives from Vector<T>. Further, we can extract
BasicVector for the purposes of vector<bool>:

    template <class self, typename T> concept BasicVector {
        void push_back(T x);
    };

    template <class self, typename T> concept Vector : BasicVector {
        T front() const;
    };

    template <class T> class vector : public Vector {
        void push_back(T x) { ... }
        T front() const { ... }
    };

    template <> class vector<bool> : public BasicVector {
        void push_back(T x) { ... }
    };

    // Operates on both vector<T> and vector<bool>
    template <class T>
    void foo(BasicVector<T>& vec, T x) {
        vec.push_back(x);
    }

    // Operates on vector<T>, but not on vector<bool>
    template <class T>
    void bar(Vector<T>& vec) {
        T& x = vec.front();
    }

Now both foo() and bar() can be compiled separately, because all
constraints are clearly expressed.


> > What I'm afraid is that we will have the concepts that will still
> > require possible yet incompatible redesign to support the separate
> > compilation. The possibility could be discovered and proven many years
> > later, but it will be too late to redesign them. Hence the risk.
>
> Fair enough. If you are really concerned about this, the best thing you
> can do is to determine what design decisions prevent us from providing
> "separate compilation" (however you want to define it) with concepts.

I don't know what "prevents us", because I'm trying to figure out how
to provide the separate compilation your way. Moreover, I suspect you
can provide the separate compilation using the technique described
above, because we can declare a concept map instead of "derive from".

The _first_ difference is that we should declare the concept map
externally, literally repeating the definitions of the primary concept,
and I doubt it's worth enough. So I think it doesn't "prevent us",
but rather it doesn't encourage our use of possible technique:

    concept Vector<T> {
        void push_back(T x);
        // + 20 constraining methods with useless default definitions
    };

    template <class T
    class vector {
        void push_back(T x);
        // + the same 20 methods, default definitions ignored
    };

    template <class T>
    concept_map Vector<vector<T> > {
        void push_back(T x);
        // + the same 20 methods, copy-pasted from Vector<T>
    };

That's almost it, except that we casually define the concept_map for
vector<bool> too, and it's the _second_ difference: we didn't want it.
I want to bind the concept Vector<T> and non-specialized vector<T>,
but not yet any specialization of vector<T>.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Wed, 8 Nov 2006 18:29:34 CST
Raw View
Douglas Gregor wrote:
> Andrei Polushin wrote:
>> I would like to recall a better syntax, quoted at the beginning
>> of this post:
>>
>>     // keyword "class" denotes constrained class
>>     // keyword "typename" denotes another parameter
>>
>>     template <class T, typename U = T>
>>     concept EqualityComparableTo {
>>         bool operator==(U) = 0;    // member of T
>>         bool U::operator==(T) = 0; // member of U - accepts T
>>     }
>
> Oh, that's subtle. Especially given that "T" in the example below
> doesn't actually have to be a class.
> Do you want to teach programmers that "class" and "typename" mean
> exactly the same thing in templates, unless that template happens to be
> a concept?

Yes :)

But we can call them "constrained_class" and "another_typename",
or even "typename self_type" instead of "class T" if somebody feels
that another keyword is worth to introduce. I think it's a matter of
taste and syntax.


>>> We have qualified the second operator==, but rather than
>>> being "T::U::operator==", as the first might suggest, it's just
>>> "U::operator==". I find that nonuniformity rather unsatisfying.
>> Could be resolved this way:
>>
>>     // parameter U is already constrained
>>
>>     template <class T, EqualityComparableTo<T> U = T>
>>     concept EqualityComparableTo {
>>         bool operator==(U) = 0;    // member of T
>>     }
>>
>> Here we constrain exactly one type in each concept.
>
> Sounds very limiting; see David Abrahams' examples.

But see my answers to them.


>> And I'm rather satisfied with that uniformity :)
>
> Are you satisfied with the recursive constraint specification? "int" is
> only EqualityComparableTo<long> if "long" is EqualityComparableTo<int>,
> which only holds if "int" is EqualityComparableTo<long>...

When we evaluating EqualityComparableTo<long> within the context of
EqualityComparableTo<int>, we are already in assumtion that
EqualityComparableTo<int> matched, so recursion is not infinite.

This is just how the preprocessor works:

    #define INT  LONG
    #define LONG INT
    INT     // expands to LONG without recursion
    LONG    // expands to INT  without recursion

I've never had problems with that.


>> I would like the concepts as base classes, so I need no difference.
>
> I have a hard time understanding how the "SameType" concept, which
> states that its two type parameters are precisely the same type (not
> subclasses of the same type), can work when concepts are base classes.
>
> If A inherits SameType<B>, and A == B, then B inherits SameType<A>...
> [...]

You don't have to inherit from the SameType, because you are not
allowed to. Really, you cannot inherit A from A.

> Note that if you can't handle SameType, you can't express the concept
> constraints in the C++ Standard Library.

Thank you for the task :) At first, I would try the following:

    template <class T, typename U> concept SameType; // incomplete
    template <class T> concept SameType<T,T> {};     // complete

Primary concept is always incomplete, and the specialization is
available only for the same types. If we like it to be the same type
in a common sense, we should promote the "template typedef" proposal:

    template <class T> typedef T SameType<T,T>;

For the lack of "template typedef", we need to workaround it in our
usual way:

    template <class T, typename U> concept SameType {
        // no contents
    };
    template <class T> typedef T SameType<T,T> {
        typedef T type;
    };

    // Usage example:
    template<typename T, typename U>
      where <SameType<U>::type T> // (see [Note 1] below)
      void f(T, U);

    // Inheritance example:
    class A : SameType::type {};  // error: cannot inherit A from A

[Note 1] It differs from your treatment of concept specializations,
because you have *intentionally* break the analogy to template
specializations (only primary concept specifies the constraints),
which makes the "type-of-a-type" view to appear broken. Probably,
this was the reason why you invent the concept maps instead of concept
specializations - because you cannot teach an old syntax with that new
semantics.


>> There are useful applications of this feature: we can use them
>> in place of CRTP, or use them to implement mix-in classes.
>
> We already do CRTP and mix-in classes, and concepts support those uses.

I don't understand. Do you support something like this:

  class A {   // an interface
  public:
    virtual int  get_a() const = 0;
    virtual void set_a(int value) = 0;
  };

  // default implementation of A
  template <class T> concept AImpl {
  private:
    int a = 0;  // pure requirement for private data member
  public:
    int  get_a() const    { return a; }
    void set_a(int value) { a = value; }
  };

  // have both interface and the default implementation
  class D : public A, public AImpl {
  private:
    int a;
  };

Currently we use CRTP for this, which requires unsafe static_cast.
We can use concepts, if they were base classes.

Or do you support this use:

  template <class T>
  concept Comparable {
    int compare(T) const = 0; // pure requirement

    friend bool operator==(T a, T b) { return compare(a, b) == 0; }
    friend bool operator!=(T a, T b) { return compare(a, b) != 0; }
    friend bool operator< (T a, T b) { return compare(a, b) <  0; }
    friend bool operator> (T a, T b) { return compare(a, b) >  0; }
    friend bool operator<=(T a, T b) { return compare(a, b) <= 0; }
    friend bool operator>=(T a, T b) { return compare(a, b) >= 0; }
  };

  template < /* ... */ >
  class basic_string {
  public:
    int compare(T) const { /* ... */ }

    using Comparable; // check the constraints, and
                      // inject other operators
  };

It illustrates the need for mix-in class Comparable, whose
implementation is injected with the "using" keyword. You did propose
something similar in N1849 "4.5.5 Exporting defaulted requirements",
but the idea looks like abandoned, is it?


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Wed, 8 Nov 2006 22:28:58 CST
Raw View
Douglas Gregor wrote:
> Andrei Polushin wrote:
>> Douglas Gregor wrote:
>> ?? Yes, concepts will greatly simplify the task of separate compilation
>> of templates because they provide all neccessary type-checking.
>> Concepts are predictable "interfaces" for template code, and the code
>> that provides a predictable interface could be compiled separately.
>> [...]
>> Thus the "export" problem could be solved, I don't know why you didn't
>> mention that benefit in your proposal.
>
> Export might be easier to implement for constrained templates; I don't
> really know. Actual separate compilation would be an interesting
> research project, but there are significant challenges ahead.

That's a pity. And I would say it's a rather risky claim. If the
concepts will be designed in a way that does not make the separate
compilation possible, while we believe they could be designed that
"right" way, that could be a show-stopper in accepting concepts
themselves in a near future.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Thu, 9 Nov 2006 10:17:41 CST
Raw View
Andrei Polushin wrote:
> Douglas Gregor wrote:
> > Export might be easier to implement for constrained templates; I don't
> > really know. Actual separate compilation would be an interesting
> > research project, but there are significant challenges ahead.
>
> That's a pity. And I would say it's a rather risky claim.

Not risky at all. We know what it takes to do separate compilation for
concepts, because it's been done. Jeremy Siek's G language provides
concepts very similar to what we're proposing for C++ (G's concepts
came first), and it provides separate compilation. However, it does NOT
provide specializaiton and it limits concept-based overloading, because
those *break* separate compilation.

> If the
> concepts will be designed in a way that does not make the separate
> compilation possible, while we believe they could be designed that
> "right" way, that could be a show-stopper in accepting concepts
> themselves in a near future.

What do you think you'll gain with separate compilation of templates?
The ability to put template definitions in .cxx files? Faster compiles?

What are you willing to give up to get separate compilation of
templates? Template specialization? Run-time performance?

Separate compilation is not a checkbox in a feature list that you
decide to provide or decide not to provide. It interacts with many
other features, so it's an optimization problem: what are your goals,
and which features most closely align with those goals? Our goals are
layed out in N2081. If you want separate compilation, you have to say
which of those goals is less important than separate compilation is.

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Thu, 9 Nov 2006 10:16:46 CST
Raw View
Andrei Polushin wrote:
> Douglas Gregor wrote:
> > Are you satisfied with the recursive constraint specification? "int" is
> > only EqualityComparableTo<long> if "long" is EqualityComparableTo<int>,
> > which only holds if "int" is EqualityComparableTo<long>...
>
> When we evaluating EqualityComparableTo<long> within the context of
> EqualityComparableTo<int>, we are already in assumtion that
> EqualityComparableTo<int> matched, so recursion is not infinite.

Really? So to determine if T1 is EqualityComparableTo<T2>, we assume
that it is. Then we check whether T2 is EqualityComparableTo<T1>, which
it is because we're assuming T1 is EqualityComparable<T2>, therefore T1
is EqualityComparableTo<T2>. With that circular reasoning, all types
are EqualityComparableTo all other types. Not exactly a useful concept
definition.

> This is just how the preprocessor works:
>
>     #define INT  LONG
>     #define LONG INT
>     INT     // expands to LONG without recursion
>     LONG    // expands to INT  without recursion
>
> I've never had problems with that.

That is completely different. The preprocessor doesn't expand tokens
recursively; it's not proving or checking anything about types.

[snip]
> You don't have to inherit from the SameType, because you are not
> allowed to. Really, you cannot inherit A from A.

I thought you wanted concepts to be base classes? You said:
> >> I would like the concepts as base classes, so I need no difference.

>     template <class T, typename U> concept SameType; // incomplete
>     template <class T> concept SameType<T,T> {};     // complete
>
> Primary concept is always incomplete, and the specialization is
> available only for the same types.

Oh no, this is the wrong way to go. You can't type check a template
based on the presence or absence of specializations. Template
metaprogramming tricks don't apply to type checking; in fact, they
*break* every type system I've ever heard of.

In any case, in this SameType concept, "T" is the "self_type" or "this"
type. That type isn't inheriting from SameType?

>     // Usage example:
>     template<typename T, typename U>
>       where <SameType<U>::type T> // (see [Note 1] below)
>       void f(T, U);
>
>     // Inheritance example:
>     class A : SameType::type {};  // error: cannot inherit A from A

What happened to the other type parameter to SameType? Isn't the usage
example above (the function template "f"), saying that SameType<U> is a
base class of T, because you want concepts to be base classes?

> [Note 1] It differs from your treatment of concept specializations,
> because you have *intentionally* break the analogy to template
> specializations (only primary concept specifies the constraints),

Because template specializations don't work with separate type
checking.

> which makes the "type-of-a-type" view to appear broken.

That's not why it's broken. True multi-type concepts, like SameType,
show why that view is broken.

> Probably,
> this was the reason why you invent the concept maps instead of concept
> specializations - because you cannot teach an old syntax with that new
> semantics.

And the old semantics does not work with a type system.

> > We already do CRTP and mix-in classes, and concepts support those uses.
>
> I don't understand. Do you support something like this:
[snip example]

> Currently we use CRTP for this, which requires unsafe static_cast.
> We can use concepts, if they were base classes.

I don't see the 'T' in 'CRTP'. Want to put requirements on a type? Do
it:

  concept A<typename T> {
    int T::get_a() const;
    void T::set_a(int value);
  };

Want to use CRTP to derive from anything that is an A? Okay:

  template<A T>
  class D : public A {
  };

Now, there are no "pure" requirements for data members. If that were a
C++ feature today, or a planned feature for C++0x, concepts would
probably support it. Frankly, I've never seen a compelling use case for
it, and it's not on the table for any version of C++.

> Or do you support this use:
[snip example]
> It illustrates the need for mix-in class Comparable, whose
> implementation is injected with the "using" keyword. You did propose
> something similar in N1849 "4.5.5 Exporting defaulted requirements",
> but the idea looks like abandoned, is it?

Yes, it's been abandoned. You can still inject operators by deriving
from a class that contains operators (as in the Boost Operators
library).

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Thu, 9 Nov 2006 11:22:58 CST
Raw View
Andrei Polushin wrote:

> In short, it's like the following:
>
> unique_copy(InputIterator,  InputIterator,  OutputIterator);
> unique_copy(ForwardIterator,ForwardIterator,OutputIterator);
> unique_copy(InputIterator,  InputIterator,  MutableForwardIterator);
> unique_copy(ForwardIterator,ForwardIterator,MutableForwardIterator);
>
> where the 4th overload resolves an ambiguity between 2nd and 3rd.

You've ignored the Assignable and CopyConstructible requirements.

> In general, 2 different requirements for each of N parameters will
> require 2*N overloads.

It can require an exponential number of overloads, in the worst case.
For every "tie-breaker" overload you add to deal with an ambiguity, you
need to ensure that the tie-breaker itself does not create any
ambiguities.

> It's bad, but knowing in advance that another
> overload exists is worse, IMHO.

You can always fall back to writing the overloads, but when you know
you'll have several related implementations, NOT constraints are the
right way to direct overloading when its needed.

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Thu, 9 Nov 2006 12:51:00 CST
Raw View
Douglas Gregor wrote:
> Andrei Polushin wrote:
>> Douglas Gregor wrote:
>>> Are you satisfied with the recursive constraint specification? "int" is
>>> only EqualityComparableTo<long> if "long" is EqualityComparableTo<int>,
>>> which only holds if "int" is EqualityComparableTo<long>...
>> When we evaluating EqualityComparableTo<long> within the context of
>> EqualityComparableTo<int>, we are already in assumtion that
>> EqualityComparableTo<int> matched, so recursion is not infinite.
> Really? So to determine if T1 is EqualityComparableTo<T2>, we assume
> that it is. Then we check whether T2 is EqualityComparableTo<T1>, which
> it is because we're assuming T1 is EqualityComparable<T2>, therefore T1
> is EqualityComparableTo<T2>. With that circular reasoning, all types
> are EqualityComparableTo all other types. Not exactly a useful concept
> definition.

That's not it. We can say that

    "X is larger than Y" if "Y is smaller than X", and
    "Y is smaller than X" if "X is larger than Y".

It's logically correct, but it leads to no conclusion until we fix it
with the exact definition of either "smaller" or "larger" (but we need
not to have the definition for both):

    template <class X, SmallerThan<X> Y>
    concept LargerThan;             // incomplete

    template <class Y, LargerThan<Y> X>
    concept SmallerThan { };        // complete

    template <>
    concept LargerThan<X1,Y1>;      // sumption: X1 is larger than Y1

    where <SmallerThan<X1> Y1>;     // conclusion: Y1 smaller than X1

That's why in EqualityComparableTo we need to have the requirement for
operator==() - it makes the right conclusions possible. The concept
without any additional constraints is silly:

    template <class X, RelatesTo<X> Y>
    concept RelatesTo {
    };

It says that if "X relates to Y", then "Y relates to X" somehow.
It's still correct, though.


>> You don't have to inherit from the SameType, because you are not
>> allowed to. Really, you cannot inherit A from A.
>
> I thought you wanted concepts to be base classes? You said:
>>>> I would like the concepts as base classes, so I need no difference.

I want as much as possible :)


>>     template <class T, typename U> concept SameType; // incomplete
>>     template <class T> concept SameType<T,T> {};     // complete
>>
>> Primary concept is always incomplete, and the specialization is
>> available only for the same types.
>
> Oh no, this is the wrong way to go. You can't type check a template
> based on the presence or absence of specializations. Template
> metaprogramming tricks don't apply to type checking; in fact, they
> *break* every type system I've ever heard of.

Not sure.


> In any case, in this SameType concept, "T" is the "self_type" or "this"
> type. That type isn't inheriting from SameType?

You are right, I use an incorrect terminology.

No, type T has an "is-a" relationship to the SameType. We often call it
"inheritance", but "is-a" could be either inheritance or the SameType.


>>     // Usage example:
>>     template<typename T, typename U>
>>       where <SameType<U>::type T> // (see [Note 1] below)
>>       void f(T, U);
>>
>>     // Inheritance example:
>>     class A : SameType::type {};  // error: cannot inherit A from A
>
> What happened to the other type parameter to SameType? Isn't the usage
> example above (the function template "f"), saying that SameType<U> is a
> base class of T, because you want concepts to be base classes?

With the "is-a" relationship, I would say it simpler:

       // Usage example:
       template<typename T, typename U>
         where <T U, U T>   // ((T is U) && (U is T)) => SameType<T,U>
         void f(T, U);

There is no need for the SameType concept.


>> [Note 1] It differs from your treatment of concept specializations,
>> because you have *intentionally* break the analogy to template
>> specializations (only primary concept specifies the constraints),
>
> Because template specializations don't work with separate type
> checking.

Not sure again. I need more time to continue on this.


>>> We already do CRTP and mix-in classes, and concepts support those uses.
>> I don't understand. Do you support something like this:
> [snip example]

Restore example:

  class A { ... };   // an interface, plain old interface

  template <class T>
  concept class AImpl { ... };  // was CRTP, now became concept

  class D : public A, public AImpl { ... }; // the one who benefits

>> Currently we use CRTP for this, which requires unsafe static_cast.
>> We can use concepts, if they were base classes.
>
> I don't see the 'T' in 'CRTP'. Want to put requirements on a type? Do
> it: [...] Want to use CRTP to derive from anything that is an A? Okay:

Sorry for confusion.

With CRTP I write AImpl<> this way:

  template <class T, class baseT = A>
  class AImpl : public baseT {          // (1) needs to have baseT
  private:
    // int a = 0;                       // (2) implicit requirement
  public:
    int  get_a() const
         { return static_cast<T*>(this)->a; }   // (3) unsafe
    void set_a(int value)
         { static_cast<T*>(this)->a = value; }  // (3) unsafe
  };

  class D : public AImpl<A> {
  public:
    int a; // (4) needs to be public, or declare friend AImpl<A>
  };

With "concepts as base classes" I want to write AImpl<> this way:

  template <class T> concept AImpl {    // (1) no baseT
  private:
    int a = 0;                          // (2) explicit requirement
  public:
    int  get_a() const    { return a; }     // (3) safe
    void set_a(int value) { a = value; }    // (3) safe
  };

  class D : public A, public AImpl {
  private:
    int a;  // (4) doesn't need to be public
  };

Nothing else. Only four differences.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Thu, 9 Nov 2006 13:23:07 CST
Raw View
Douglas Gregor wrote:
> Andrei Polushin wrote:
>> If the
>> concepts will be designed in a way that does not make the separate
>> compilation possible, while we believe they could be designed that
>> "right" way, that could be a show-stopper in accepting concepts
>> themselves in a near future.
>
> What do you think you'll gain with separate compilation of templates?
> The ability to put template definitions in .cxx files? Faster compiles?

Both.

> What are you willing to give up to get separate compilation of
> templates? Template specialization? Run-time performance?

None.

> Separate compilation is not a checkbox in a feature list that you
> decide to provide or decide not to provide. It interacts with many
> other features, so it's an optimization problem: what are your goals,
> and which features most closely align with those goals? Our goals are
> layed out in N2081. If you want separate compilation, you have to say
> which of those goals is less important than separate compilation is.

Thus you leave me a choice between two evils. I don't think I have
to choose. In particular, with your example

>   template<CopyConstructible T>
>   void foo(vector<T>& vec) {
>     T& x = vec.front();
>   }

I would prefer to have two separately compiled versions of foo, one
for the primary template of vector, and another for the vector<bool>,
because they are the only specializations foo sees at the point of
its definition. The second definition should fail to compile, so the
author of foo should provide (or at least forward declare) a suitable
overload. Then if anybody creates specialization for vector<int>, he
will not have the definition of foo, because there was no vector<int>
when foo gets compiled.

It differs from "export", but it is a separate compilation, while the
"export" is not.

What I'm afraid is that we will have the concepts that will still
require possible yet incompatible redesign to support the separate
compilation. The possibility could be discovered and proven many years
later, but it will be too late to redesign them. Hence the risk.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Thu, 9 Nov 2006 13:58:38 CST
Raw View
===================================== MODERATOR'S COMMENT:
 Posts may sometimes take more than 24 hours to be approved, though generally our turnaround is faster.  Posts may also appear in an order other than that in which they were posted, largely as a result of how the moderation software works.


===================================== END OF MODERATOR'S COMMENT
{resending this post, because it seems to be received, but neither
approved, nor rejected within 24 hours -author}

David Abrahams wrote:
> > "Andrei Polushin" writes:
>> >>
>> >>     template <class T, EqualityComparableTo<T> U = T>
>> >>     concept EqualityComparableTo {
>> >>         bool operator==(U) = 0;
>> >>     }
> >
> > This only works for "dependent" multi-type concepts.  What you can't
> > do is write a matrix-vector multiplication function that requires that
> > together, the matrix and vector satisfy the VectorSpace<M,V> concept.
> >
> >   template <class M, class V>
> >   where VectorSpace<M,V>
> >   V mul(M m, V v)
> >   {
> >       ...
> >   }

Why can't I write it your way?:

    template <typename M, typename V>
       where <VectorSpace<M,V> S>
    V mul(M m, V v)
    {
        ...
    }

If you'll see it as many-to-many relationship between M and V, then
the intermediate entity S would be appropriate.

Other way is to extract a common entity from both M and V, then check
it for compatibility:

    template <PartOfSpace M, PartOfSpace V>
       where <CompatibleSpace<M::vector_space> V::vector_space>
    V mul(M m, V v)
    {
        ...
    }

This is exactly what we do to interrelate InputIterator to
OutputIterator using their "reference" types.


> > and, no, I don't consider
> >
> >   template <class M, class V>
> >   V mul(M m, FormsAVectorSpaceWith<M> V v)
> >   {
> >       ...
> >   }
> >
> > to be acceptable.  What happens when the first argument has to
> > be the Vector type?
> >
> >   template <class V, class M>
> >   V mul2(V v, MatrixOfAVectorSpaceWith<V> M m)
> >   {
> >       ...
> >   }
> >
> >   template <class M, class V>
> >   V mul(M m, VectorOfAVectorSpaceWith<M> V v)
> >   {
> >       ...
> >   }
> >
> > It doesn't scale.

It doesn't scale if both of my two solutions fail. Then you'll have to
describe constraints for each pair of absolutely different M and V,
which couldn't be scalable in principle.


-- Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: David Abrahams <dave@boost-consulting.com>
Date: Fri, 10 Nov 2006 02:09:16 CST
Raw View
"Douglas Gregor" <doug.gregor@gmail.com> writes:

> What are you willing to give up to get separate compilation of
> templates? Template specialization? Run-time performance?

You don't need to give up any of these things if you pick the "right"
model for "separate compilation of templates."  All you need to do is
view a template as a function that, when called (instantiated),
generates some internal representation of a non-template type or a
function in the compiler.  Right now, every compiler represents
templates as data structures -- the "bytecode" of the function -- and
"interprets" the "bytecode" to generate actual types and functions.
Instead, we could compile these data structures down to machine code
and simply execute that instead of interpreting the data structures.

All that said, I put "right" in quotes because it begs your original
question:

> What do you think you'll gain with separate compilation of
> templates?  The ability to put template definitions in .cxx files?
> Faster compiles?

We don't know whether such a scheme would achieve either of these
things.  It certainly doesn't change the programming model at all.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: David Abrahams <dave@boost-consulting.com>
Date: Fri, 10 Nov 2006 02:08:29 CST
Raw View
"Andrei Polushin" <polushin@gmail.com> writes:

>> Sounds very limiting; see David Abrahams' examples.
>
> But see my answers to them.

Either you haven't answered the examples Doug is referring to, or the
answers simply haven't shown up on my news server.  He's talking about
the VectorSpace examples.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Fri, 3 Nov 2006 22:29:36 CST
Raw View
Ganesh wrote:
> I was reading the paper "Concepts: Linguistic Support for Generic
> Programming in C++". The idea is good, but I thought one can take an
> alternative approach towards that.
[snip example]
> The core idea is this: Provide all the constraints that you want a
> template parameter to follow in a sample class and say that the
> template parameter just "ducks" the class thats given as constraint.

What if you want constraints that don't involve member functions?

  concept Swappable<typename T> {
    void swap(T&, T&);
  }

Or you want constraints that involve multiple template parameters?

  concept EqualityComparable<typename T, typename U> {
    bool operator==(T, U);
  }

Or constraints that involve nested ("associated") types, e.g., every
sequence has an iterator type whose value_type is equivalent to the
sequence's value type:

  concept Sequence<typename X> {
    typename value_type; // Sequence's value type
    MutableForwardIterator iterator; // Sequence's iterator type
    where SameType<value_type, iterator::value_type>; // they are the
same!
  }

> The advantage is that this is conceptually simple, only one more
> keyword is required, doesn't break the backward compatibility and easy
> to use and explain.

I see two major disadvantages:

  1) I believe that "ducks" won't be able to express all of the
requirements that templates actually have. It may appear simpler, but
it is an incomplete solution.

  2) Just what attributes of a class does "ducks" pick up? Its member
functions, constructors and destructors? What if they are private?
Protected? What about friends? Free functions? Static members? The
object's size? Alignment? vtable layout? Start specifying this and I
think you'll find that "ducks" isn't so simple after all. Better to
have a new "concept" (pun intended) that ties down precisely what
requirements you want to pick up.

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Sat, 4 Nov 2006 19:09:46 CST
Raw View
Douglas Gregor wrote:
> What if you want constraints that don't involve member functions?
>
>   concept Swappable<typename T> {
>     void swap(T&, T&);
>   }

BTW, why did you define concept members as free functions by default?

You have:

    concept Swappable<typename T> {
        void T::swap(T&);   // member function
        void swap(T&, T&);  // free function
    }

Could it be:

    concept Swappable<typename T> {
        void swap(T&);              // member function
        friend void swap(T&, T&);   // free function
    }

In other words, could it be more user-friendly?


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Sun, 5 Nov 2006 13:34:49 CST
Raw View
Andrei Polushin wrote:
> Douglas Gregor wrote:
> > What if you want constraints that don't involve member functions?
> >
> >   concept Swappable<typename T> {
> >     void swap(T&, T&);
> >   }
>
> BTW, why did you define concept members as free functions by default?

Several reasons, including:

  1) Because member functions are unfriendly toward built-in types
(e.g., int* can't have a member "swap", but there can be a free
function "swap" for int*'s). This is the reason that most interfaces to
templates rely on free functions and operators, rather than member
functions. Concepts need to model those kinds of interfaces.

  2) Because it would be unclear *which* parameter to the concept the
functions are a member of. For example, let's try writing an
EqualityComparable concept with member functions by default:

    concept EqualityComparable<typename T, typename U> {
      bool operator==(T);
      bool operator==(U);
    }

Are those operator== member functions in T, or in U? Or is it different
for each of them?    With the free function form, it's clear because we
have to state which type parameter the operator is a member of:

    concept EqualityComparable<typename T, typename U> {
      bool T::operator==(U);
      bool U::operator==(T);
    }

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Sun, 5 Nov 2006 19:51:41 CST
Raw View
Douglas Gregor wrote:
> Andrei Polushin wrote:
>> BTW, why did you define concept members as free functions by default?
>
> Several reasons, including:
>
>   1) Because member functions are unfriendly toward built-in types
> (e.g., int* can't have a member "swap", but there can be a free
> function "swap" for int*'s). This is the reason that most interfaces to
> templates rely on free functions and operators, rather than member
> functions. Concepts need to model those kinds of interfaces.

Got your point. Does it encourage me to abandon OO programming in
preference to free functions, like in C? While you are defining the
concepts for the C++ Standard, you are dealing primarily with the
built-in types inherited from C, so it's practically correct. But will
it still be the better solution in the modern OO world? Wouldn't it be
better to enhance those built-in types with members:

    concept Swappable<typename T> {
        void T::swap(T&) { swap(*this, T); } // member function
        bool swap(T, T);                     // free function
    };

>   2) Because it would be unclear *which* parameter to the concept the
> functions are a member of. For example, let's try writing an
> EqualityComparable concept with member functions by default:
>
>     concept EqualityComparable<typename T, typename U> {
>       bool operator==(T);
>       bool operator==(U);
>     }
>
> Are those operator== member functions in T, or in U? Or is it different
> for each of them?    With the free function form, it's clear because we
> have to state which type parameter the operator is a member of:
>
>     concept EqualityComparable<typename T, typename U> {
>       bool T::operator==(U);
>       bool U::operator==(T);
>     }

Probably, there is no need for template parameter T (just like there is
no need to pass 'this' parameter to our member functions - it is passed
implicitly all the time):

      concept EqualityComparableTo<typename U> {
        bool operator==(U);                       // member of T
        bool U::operator==(EqualityComparableTo); // member of U
      }

In other words, concept is just a template with an implicitly passed
first template-parameter. Thus you can reuse the hundreds of rules and
design issues we already have for templates, not reinventing them
specially for concepts. For example, the "concept map" could be called
a "concept specialization", modeling the template specialization, etc.

Surprisingly, you concede to this view with "inline requirements"
(N2081, section 3.4.2):

    // your syntax
    concept ConvertibleTo<typename T, typename U> {
        operator U();
    };

    // my syntax
    concept ConvertibleTo<typename U> {
        operator U();
    };

    // both yours and mine syntax
    template <typename U, ConvertibleTo<U> T>
    U convert(const T& t) {
        return t;
    }

But "inline requirements" is an exception for the rules you propose.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Mon, 6 Nov 2006 09:49:25 CST
Raw View
Andrei Polushin wrote:
> Got your point. Does it encourage me to abandon OO programming in
> preference to free functions, like in C? While you are defining the
> concepts for the C++ Standard, you are dealing primarily with the
> built-in types inherited from C, so it's practically correct.

Even in the modern world, we often prefer free functions to member
functions, and not because of the built-in types we inherited from C.
Here's what Scott Meyers has to say about prefering free functions to
improve encapsulation:

  http://www.ddj.com/dept/cpp/184401197

Free functions are also better for interoperability. It's easy to add a
new free function for a built-in type or a class that you didn't write,
but it's not so easy to add a new member function to a built-in type
(they can't have member functions!) or a class that you didn't write
(e.g., it might come from a library that you can't modify). There's an
important point here: the problems that one has with using built-in
types in an OO context (such as the inability to add a new member
function or derive from a new abstract base class) are also problems
with any classes that you didn't write.

> But will
> it still be the better solution in the modern OO world? Wouldn't it be
> better to enhance those built-in types with members:
>
>     concept Swappable<typename T> {
>         void T::swap(T&) { swap(*this, T); } // member function
>         bool swap(T, T);                     // free function
>     };

Concepts fix the inability to add new member functions, in the sense
that you can use a concept map to make it look like a certain type has
that member function (even when it doesn't). So, if you want to write
all of your concepts to use member functions rather than free
functions, go ahead: the concept system can handle it.

> >   2) Because it would be unclear *which* parameter to the concept the
> > functions are a member of. For example, let's try writing an
> > EqualityComparable concept with member functions by default:
> >
> >     concept EqualityComparable<typename T, typename U> {
> >       bool operator==(T);
> >       bool operator==(U);
> >     }
[snip]
> Probably, there is no need for template parameter T (just like there is
> no need to pass 'this' parameter to our member functions - it is passed
> implicitly all the time):
>
>       concept EqualityComparableTo<typename U> {
>         bool operator==(U);                       // member of T
>         bool U::operator==(EqualityComparableTo); // member of U
>       }

Note that this has slightly different semantics than the
EqualityComparable that uses a free operator==(T, U), because of the
way C++ treats the implicit object parameter.

> In other words, concept is just a template with an implicitly passed
> first template-parameter.

This was discussed a few years ago, and eventually rejected. The
implication of this code is that EqualityComparableTo is a type, in the
same sense that a OO interface or abstract base class is a type. This
is not the case: concepts describe requirements on types, but they are
not types themselves. Using a syntax like the above confuses the issue
greatly.

> Thus you can reuse the hundreds of rules and
> design issues we already have for templates, not reinventing them
> specially for concepts. For example, the "concept map" could be called
> a "concept specialization", modeling the template specialization, etc.

I tried to turn concept maps into concept specializations at one point,
for the same reason you mention, and it turned out to be a bad idea:
people were very confused by the use of specialization syntax, whereas
they seem to understand the concept map syntax much better.

> Surprisingly, you concede to this view with "inline requirements"
> (N2081, section 3.4.2):
>
>     // your syntax
>     concept ConvertibleTo<typename T, typename U> {
>         operator U();
>     };

That should be:

  operator U(T);

> But "inline requirements" is an exception for the rules you propose.

Inline requirements is a shortcut that is useful for many common cases.
We kept it out of the concept system for a long time because it can be
confusing (the "type-of-a-type" view of concepts breaks down
eventually). However, it's such a good shortcut syntactically that it
just kept coming back :)

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Mon, 6 Nov 2006 14:09:04 CST
Raw View
Douglas Gregor wrote:
> Even in the modern world, we often prefer free functions to member
> functions, and not because of the built-in types we inherited from C.
> Here's what Scott Meyers has to say about prefering free functions to
> improve encapsulation:
>
>   http://www.ddj.com/dept/cpp/184401197

The use of the word "encapsulation" in his article is very doubtful.
I've already answered to it:
http://groups.google.com/group/comp.lang.c++.moderated/msg/fb30c8094d82aef2


> Free functions are also better for interoperability. It's easy to add a
> new free function for a built-in type or a class that you didn't write,
> but it's not so easy to add a new member function to a built-in type
> (they can't have member functions!) or a class that you didn't write
> (e.g., it might come from a library that you can't modify). There's an
> important point here: the problems that one has with using built-in
> types in an OO context (such as the inability to add a new member
> function or derive from a new abstract base class) are also problems
> with any classes that you didn't write.
>
>> But will
>> it still be the better solution in the modern OO world? Wouldn't it be
>> better to enhance those built-in types with members:
>>
>>     concept Swappable<typename T> {
>>         void T::swap(T&) { swap(*this, T); } // member function
>>         bool swap(T, T);                     // free function
>>     };
>
> Concepts fix the inability to add new member functions, in the sense
> that you can use a concept map to make it look like a certain type has
> that member function (even when it doesn't). So, if you want to write
> all of your concepts to use member functions rather than free
> functions, go ahead: the concept system can handle it.

Rereading this and the previous part of your answer, I'm also in doubt.
Concept system *can handle* adding new member functions, so there is
*no problem* with the inability to add new member functions for foreign
classes and built-in types. Probably, concepts still support legacy
programming practices, invented for the lack of modern concepts.


>>>   2) Because it would be unclear *which* parameter to the concept the
>>> functions are a member of. For example, let's try writing an
>>> EqualityComparable concept with member functions by default:
>>>
>>>     concept EqualityComparable<typename T, typename U> {
>>>       bool operator==(T);
>>>       bool operator==(U);
>>>     }
> [snip]
>> Probably, there is no need for template parameter T (just like there is
>> no need to pass 'this' parameter to our member functions - it is passed
>> implicitly all the time):
>>
>>       concept EqualityComparableTo<typename U> {
>>         bool operator==(U);                       // member of T
>>         bool U::operator==(EqualityComparableTo); // member of U
>>       }
>
> Note that this has slightly different semantics than the
> EqualityComparable that uses a free operator==(T, U), because of the
> way C++ treats the implicit object parameter.

Yes, we define binary operators as free functions, but I was answering
to the "unclear *which* parameter to the concept the functions are a
member of", so translating your example literally, to illustrate how
it would look clear and familiar for us.


>> In other words, concept is just a template with an implicitly passed
>> first template-parameter.
>
> This was discussed a few years ago, and eventually rejected. The
> implication of this code is that EqualityComparableTo is a type, in the
> same sense that a OO interface or abstract base class is a type. This
> is not the case: concepts describe requirements on types, but they are
> not types themselves. Using a syntax like the above confuses the issue
> greatly.

We don't have the keyword "interface" in C++, but we do not hesitate
to constraint our classes by interfaces.

Really: interface is a type that constraints any class for use by the
function that operates on that interface.

Likewise: concept is a template-type that constraints any type for use
by the template-function that operates on that concept.

I'm not confused with this metaphor, but I feel its more natural.
Concept could be the type in the same sense as interface is a type.


>> Thus you can reuse the hundreds of rules and
>> design issues we already have for templates, not reinventing them
>> specially for concepts. For example, the "concept map" could be called
>> a "concept specialization", modeling the template specialization, etc.
>
> I tried to turn concept maps into concept specializations at one point,
> for the same reason you mention, and it turned out to be a bad idea:
> people were very confused by the use of specialization syntax, whereas
> they seem to understand the concept map syntax much better.

Did you try the following syntax (keyword "class" denotes constrained
class; keyword "typename" denotes another concept-parameter):

    template <class>            // primary concept (non-specialized)
    concept Iterator {
        typename value_type;        // requirement for type
        typename reference;         // requirement for type
        typename pointer;           // requirement for type

        reference operator*() = 0;  // requirement for member
        pointer   operator->()      // requirement for member
        {
            return &operator*();    // default implementation
        }
    };

    template <typename T>       // specialization for T*
    concept Iterator<T*> {
        typedef T  value_type;
        typedef T& reference;
        typedef T* pointer;

        reference operator*() = 0;
        pointer   operator->() = 0;
    };

Here I define the primary concept and the concept specialization, just
following the syntax of templates. Probably, it could be much better
understood than concept map, because we are already familiar with such
syntax.

Another issue is the "= 0" at the end of pure requirement, so we may
define the "default implementation" somewhere else, probably in a
separate translation unit. Currently, you allow this for concept maps
(N2081, 3.3.1/3), but not for concepts themselves.

There are many generalizations I see when following this way.


>> Surprisingly, you concede to this view with "inline requirements"
>> [...]
>> But "inline requirements" is an exception for the rules you propose.
>
> Inline requirements is a shortcut that is useful for many common cases.
> We kept it out of the concept system for a long time because it can be
> confusing (the "type-of-a-type" view of concepts breaks down
> eventually). However, it's such a good shortcut syntactically that it
> just kept coming back :)

I would like to see how it breaks down :)


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Mon, 6 Nov 2006 16:38:22 CST
Raw View
Andrei Polushin wrote:
> Douglas Gregor wrote:
> Probably, concepts still support legacy
> programming practices, invented for the lack of modern concepts.

Not all of us agree that the use of free functions instead of member
functions is "legacy"; I prefer to think of it as "post-modern" :)

> Really: interface is a type that constraints any class for use by the
> function that operates on that interface.
>
> Likewise: concept is a template-type that constraints any type for use
> by the template-function that operates on that concept.
>
> I'm not confused with this metaphor, but I feel its more natural.
> Concept could be the type in the same sense as interface is a type.
>
>
> Did you try the following syntax (keyword "class" denotes constrained
> class; keyword "typename" denotes another concept-parameter):
>
>     template <class>            // primary concept (non-specialized)
>     concept Iterator {
>         typename value_type;        // requirement for type
>         typename reference;         // requirement for type
>         typename pointer;           // requirement for type
>
>         reference operator*() = 0;  // requirement for member
>         pointer   operator->()      // requirement for member
>         {
>             return &operator*();    // default implementation
>         }
>     };

You can find examples of this in some of the very early work on
concepts in C++, e.g., N1510

  http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1510.pdf

It has since been rejected, in part because we need a name for the type
of the implicit "this" parameter.

>     template <typename T>       // specialization for T*
>     concept Iterator<T*> {
>         typedef T  value_type;
>         typedef T& reference;
>         typedef T* pointer;
>
>         reference operator*() = 0;
>         pointer   operator->() = 0;
>     };
>
> Here I define the primary concept and the concept specialization, just
> following the syntax of templates. Probably, it could be much better
> understood than concept map, because we are already familiar with such
> syntax.

We tried almost exactly this syntax in N1849 (but with free functions
as the basis, not member functions):


http://www.generic-programming.org/languages/conceptcpp/papers/n1849.pdf

It confused people. I'm not speculating, I'm reporting my experiences
teaching people concepts: the specialization syntax does not work.

> Another issue is the "= 0" at the end of pure requirement, so we may
> define the "default implementation" somewhere else, probably in a
> separate translation unit. Currently, you allow this for concept maps
> (N2081, 3.3.1/3), but not for concepts themselves.

Doesn't work, unfortunately. We need to check the default
implementation of a concept whenever the user writes a concept map for
that concept (or matches an "auto" concept). Even if we could get
around that (say, by making default implementations fully type-checked
when they are defined), then we'd be requiring separate compilation of
these default implementations, which amounts to separate compilation of
templates.

> >> Surprisingly, you concede to this view with "inline requirements"
> >> [...]
> >> But "inline requirements" is an exception for the rules you propose.
> >
> > Inline requirements is a shortcut that is useful for many common cases.
> > We kept it out of the concept system for a long time because it can be
> > confusing (the "type-of-a-type" view of concepts breaks down
> > eventually). However, it's such a good shortcut syntactically that it
> > just kept coming back :)
>
> I would like to see how it breaks down :)

Let's look at this example again:

      concept EqualityComparableTo<typename U> {
        bool operator==(U);                       // member of T
        bool U::operator==(EqualityComparableTo); // member of U
      }

First, a point on syntax: we've not qualified the first operator==, so
it's a part of the implicit type "T"; effectively, we're looking for
T::operator==. We have qualified the second operator==, but rather than
being "T::U::operator==", as the first might suggest, it's just
"U::operator==". I find that nonuniformity rather unsatisfying.

Now, that EqualityComparableTo parameter in the second operator==...
does that mean
  (1) The type of the implicit "this" parameter, what we've been
calling "T"?, or
  (2) Any type that meets the requirements of EqualityComparableTo<U>?

If it's (1), we now have a big difference from the way
interfaces/abstract base classes work, because they are based on a
subtyping relationship. That's doable, but it weakens the OO analogies
significantly.

If it's (2), we now have an operator== that is effectively a template,
because the right-hand side can accept *any* type that meets the
requirements of EqualityComparableTo<U>. This is okay in OO land,
because one would just use virtual functions in U to do the
comparison... but we don't have virtual functions in the world of
concepts.

I think the issues might crop up with non-parameterized concepts. If
one wrote something like this:

  concept EqualityComparable1 {
    bool operator==(EqualityComparable1);
  }

Aside from concept maps and their use in adaptation of syntax, how does
that differ from the abstract base class:

  class EqualityComparable {
    bool equals(const EqualityComparable&) = 0;
  };

?

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Mon, 6 Nov 2006 23:04:45 CST
Raw View
Douglas Gregor wrote:
> Andrei Polushin wrote:
>> Did you try the following syntax (keyword "class" denotes constrained
>> class; keyword "typename" denotes another concept-parameter):
>>
>>     template <class>            // primary concept (non-specialized)
>>     concept Iterator {
>>         typename value_type;        // requirement for type
>>         typename reference;         // requirement for type
>>         typename pointer;           // requirement for type
>>
>>         reference operator*() = 0;  // requirement for member
>>         pointer   operator->()      // requirement for member
>>         {
>>             return &operator*();    // default implementation
>>         }
>>     };
>
> [...]
> It has since been rejected, in part because we need a name for the type
> of the implicit "this" parameter.

I've omitted that name for the purposes of the example above. You can
read it as

       template <class T>          // implicit type T is unused
       concept Iterator {
           // ...
       }

>>     template <typename T>       // specialization for T*
>>     concept Iterator<T*> {
>>         typedef T  value_type;
>>         typedef T& reference;
>>         typedef T* pointer;
>>
>>         reference operator*() = 0;
>>         pointer   operator->() = 0;
>>     };

(I'm quoting this just for context).


>> Another issue is the "= 0" at the end of pure requirement, so we may
>> define the "default implementation" somewhere else, probably in a
>> separate translation unit. Currently, you allow this for concept maps
>> (N2081, 3.3.1/3), but not for concepts themselves.
>
> Doesn't work, unfortunately. We need to check the default
> implementation of a concept whenever the user writes a concept map for
> that concept (or matches an "auto" concept). Even if we could get
> around that (say, by making default implementations fully type-checked
> when they are defined), then we'd be requiring separate compilation of
> these default implementations, which amounts to separate compilation of
> templates.

?? Yes, concepts will greatly simplify the task of separate compilation
of templates because they provide all neccessary type-checking.
Concepts are predictable "interfaces" for template code, and the code
that provides a predictable interface could be compiled separately.
Thus the "export" problem could be solved, I don't know why you didn't
mention that benefit in your proposal.

BTW, by "separate compilation" I mean a compilation into some specific
"byte code", not into binary objects.


>>>> Surprisingly, you concede to this view with "inline requirements"
>>>> [...]
>>>> But "inline requirements" is an exception for the rules you propose.
>>> Inline requirements is a shortcut that is useful for many common cases.
>>> We kept it out of the concept system for a long time because it can be
>>> confusing (the "type-of-a-type" view of concepts breaks down
>>> eventually). However, it's such a good shortcut syntactically that it
>>> just kept coming back :)
>> I would like to see how it breaks down :)
>
> Let's look at this example again:
>
>       concept EqualityComparableTo<typename U> {
>         bool operator==(U);                       // member of T
>         bool U::operator==(EqualityComparableTo); // member of U
>       }
>
> First, a point on syntax: we've not qualified the first operator==, so
> it's a part of the implicit type "T"; effectively, we're looking for
> T::operator==. We have qualified the second operator==, but rather than
> being "T::U::operator==", as the first might suggest, it's just
> "U::operator==". I find that nonuniformity rather unsatisfying.
>
> Now, that EqualityComparableTo parameter in the second operator==...
> does that mean
>   (1) The type of the implicit "this" parameter, what we've been
> calling "T"?, or
>   (2) Any type that meets the requirements of EqualityComparableTo<U>?

I would like to recall a better syntax, quoted at the beginning
of this post:

    // keyword "class" denotes constrained class
    // keyword "typename" denotes another parameter

    template <class T, typename U = T>
    concept EqualityComparableTo {
        bool operator==(U) = 0;    // member of T
        bool U::operator==(T) = 0; // member of U - accepts T
    }

Now the answer is: (1) The type of the implicitly passed T parameter.
(This parameter is *passed* implicitly, but we still can mention it
everywhere in concept, and create specializations on it.)

Now the prevous question

> We have qualified the second operator==, but rather than
> being "T::U::operator==", as the first might suggest, it's just
> "U::operator==". I find that nonuniformity rather unsatisfying.

Could be resolved this way:

    // parameter U is already constrained

    template <class T, EqualityComparableTo<T> U = T>
    concept EqualityComparableTo {
        bool operator==(U) = 0;    // member of T
    }

Here we constrain exactly one type in each concept.
And I'm rather satisfied with that uniformity :)


> I think the issues might crop up with non-parameterized concepts. If
> one wrote something like this:
>
>   concept EqualityComparable1 {
>     bool operator==(EqualityComparable1);
>   }
>
> Aside from concept maps and their use in adaptation of syntax, how does
> that differ from the abstract base class:
>
>   class EqualityComparable {
>     bool equals(const EqualityComparable&) = 0;
>   };

I would like the concepts as base classes, so I need no difference.
There are useful applications of this feature: we can use them
in place of CRTP, or use them to implement mix-in classes.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: David Abrahams <dave@boost-consulting.com>
Date: Tue, 7 Nov 2006 00:37:57 CST
Raw View
"Douglas Gregor" <doug.gregor@gmail.com> writes:

> Andrei Polushin wrote:
>
>> Douglas Gregor wrote:
>>
>> > the "type-of-a-type" view of concepts breaks down
>> > eventually
   ...
>>
>> I would like to see how it breaks down :)
>
> Let's look at this example again:

<snip long example>

not to mention the much simpler fact that the "type-of-a-type" view
doesn't support multi-type concepts.  Justifying the need for
multi-type concepts takes a little doing, but I think it might be a
lot less text than the long example.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Tue, 7 Nov 2006 14:20:41 CST
Raw View
David Abrahams wrote:
> "Douglas Gregor" <doug.gregor@gmail.com> writes:
>> Andrei Polushin wrote:
>>> Douglas Gregor wrote:
>>>> the "type-of-a-type" view of concepts breaks down
>>>> eventually
>>>> ...
>>> I would like to see how it breaks down :)
>> Let's look at this example again:
>
> <snip long example>
>
> not to mention the much simpler fact that the "type-of-a-type" view
> doesn't support multi-type concepts.  Justifying the need for
> multi-type concepts takes a little doing, but I think it might be a
> lot less text than the long example.

You do not provide an example, so I was stumbled to understand what
problem you mean. Then I've found that N2081 prohibits use of inline
requirements shortcut for multi-type parameters (3.4.2/4).
In contrast, I did provide an example in another post, in assumption
that this syntax is already allowed:

    // EqualityComparableTo<T> is not allowed by N2081

    template <class T, EqualityComparableTo<T> U = T>
    concept EqualityComparableTo {
        bool operator==(U) = 0;
    }

That prohibition looks strange for me, but I've discovered that Doug
also tends to allow it (in article from his blog):
http://conceptgcc.wordpress.com/2006/06/02/parameterized-types-of-types/

It has the status of "shortcut" there, but we see how it simplifies
the whole story (32 lines => 21 lines => 13 lines, see the article):

  template<InputIterator Iter1, InputIterator Iter2, typename T,
           typename BinOp1,
           Callable2<Iter1::reference, Iter2::reference> BinOp2>
    where Callable2<BinOp1, T, BinOp2::result_type>&&
          Assignable<
            T,
            Callable2<BinOp1, T, BinOp2::result_type>::result_type
          >
    T
    inner_product(Iter1 first1, Iter1 last1,
                  Iter2 first2, T init,
                  BinOp1 binary_op1,
                  BinOp2 binary_op2)

Now imaging the following "type-of-a-type" syntax for the constrained
templates:

  template-declaration:
    *template* </template-parameter-list/> /where-clause/ /declaration/

  where-clause:
    *where* </template-parameter-list/>

(only 2 syntactic rules; compare to syntax in N2081 3.4 and 3.4.1).

Having this, I can redefine the inner_product in a shorter way:

  template<InputIterator Iter1, InputIterator Iter2, typename T,
           typename BinOp1,
           Callable2<Iter1::reference, Iter2::reference> BinOp2>
    where <Callable2<T, BinOp2::result_type> BinOp1,
           Assignable<BinOp1::result_type>   T>
    T
    inner_product(Iter1 first1, Iter1 last1,
                  Iter2 first2, T init,
                  BinOp1 binary_op1,
                  BinOp2 binary_op2)

Note that I've used the "type-of-a-type" view for the rather complex
example and encounter no problem. There is still the need for the
"where" clause, but I do not even try to argue against it.

Other example constraints the nested type:

  template<InputIterator Iter1, ForwardIterator Iter2>
    where <EqualityComparableTo<Iter1::reference> Iter2::reference>
    Iter1 find_first_of(Iter1 first1, Iter1 last1,
                        Iter2 first2, Iter2 last2);

The comma inside the where<,,,> clause is an AND constraint. Let's
try to consider the need for the OR and NOT constraints.

An example with OR constraint (the authors of N2081 are in doubt
about the need for OR constraint):

    template<Integral T> T abs(T x) { return x < 0 ? -x : x; }
    template<Floating T> T abs(T x) { return x < 0 ? -x : x; }

Two templates - is it bad? With the "type-of-a-type" view, we are
suggested to introduce a base concept "Signed":

    template<Signed T> T abs(T x) { return x < 0 ? -x : x; }

Thus I see no need for OR constraint.

The NOT constraint is rather more redundant: it is "used to direct
concept-based overloading", so that the overload *knows in advance*
that another overload exists. The result is identical once we have
the declarations of both overloads, so I think we don't need the
NOT constraint.

I still hope to see how the "type-of-a-type" view breaks down.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: dave@boost-consulting.com (David Abrahams)
Date: Wed, 8 Nov 2006 05:40:00 GMT
Raw View
"Andrei Polushin" <polushin@gmail.com> writes:

> David Abrahams wrote:
>> "Douglas Gregor" <doug.gregor@gmail.com> writes:
>>> Andrei Polushin wrote:
>>>> Douglas Gregor wrote:
>>>>> the "type-of-a-type" view of concepts breaks down
>>>>> eventually
>>>>> ...
>>>> I would like to see how it breaks down :)
>>> Let's look at this example again:
>>
>> <snip long example>
>>
>> not to mention the much simpler fact that the "type-of-a-type" view
>> doesn't support multi-type concepts.  Justifying the need for
>> multi-type concepts takes a little doing, but I think it might be a
>> lot less text than the long example.
>
> You do not provide an example, so I was stumbled to understand what
> problem you mean. Then I've found that N2081 prohibits use of inline
> requirements shortcut for multi-type parameters (3.4.2/4).
> In contrast, I did provide an example in another post, in assumption
> that this syntax is already allowed:
>
>     // EqualityComparableTo<T> is not allowed by N2081
>
>     template <class T, EqualityComparableTo<T> U = T>
>     concept EqualityComparableTo {
>         bool operator==(U) = 0;
>     }

This only works for "dependent" multi-type concepts.  What you can't
do is write a matrix-vector multiplication function that requires that
together, the matrix and vector satisfy the VectorSpace<M,V> concept.

  template <class M, class V>
  where VectorSpace<M,V>
  V mul(M m, V v)
  {
      ...
  }

and, no, I don't consider

  template <class M, class V>
  V mul(M m, FormsAVectorSpaceWith<M> V v)
  {
      ...
  }

to be acceptable.  What happens when the first argument has to
be the Vector type?

  template <class V, class M>
  V mul2(V v, MatrixOfAVectorSpaceWith<V> M m)
  {
      ...
  }

  template <class M, class V>
  V mul(M m, VectorOfAVectorSpaceWith<M> V v)
  {
      ...
  }

It doesn't scale.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Wed, 8 Nov 2006 15:12:49 CST
Raw View
Andrei Polushin wrote:
> You do not provide an example, so I was stumbled to understand what
> problem you mean. Then I've found that N2081 prohibits use of inline
> requirements shortcut for multi-type parameters (3.4.2/4).

3.4.1/1 explicitly allows the use of inline requirements for multi-type
parameters. 3.4.2/4 only deals with multi-type parameters that have
defaulted arguments after the first argument.

> An example with OR constraint (the authors of N2081 are in doubt
> about the need for OR constraint):
>
>     template<Integral T> T abs(T x) { return x < 0 ? -x : x; }
>     template<Floating T> T abs(T x) { return x < 0 ? -x : x; }
>
> Two templates - is it bad? With the "type-of-a-type" view, we are
> suggested to introduce a base concept "Signed":
>
>     template<Signed T> T abs(T x) { return x < 0 ? -x : x; }
>
> Thus I see no need for OR constraint.

Right. The current formulation of concepts supports the same notion;
just write a "Signed" concept (with one type parameter). Integral and
Floating would either refine Signed (if you happen to be able to change
Integral and Floating!), or there would be concept maps mapping from
Integral and Floating to Signed.

> The NOT constraint is rather more redundant: it is "used to direct
> concept-based overloading", so that the overload *knows in advance*
> that another overload exists. The result is identical once we have
> the declarations of both overloads, so I think we don't need the
> NOT constraint.

There might not be an ordering among those two overloads based only on
their normal constraints. It doesn't happen often, and it doesn't
happen in the simple cases. The NOT constraint allows you to handle the
hard cases where you truly have disjoint requirements in two overloads,
but you need concept-based overloading to order them. unique_copy is a
truly intruiging example where the C++ standard describes several
variants of the algorithm that have some constraints that aren't
ordered:


http://conceptgcc.wordpress.com/2006/04/27/unique_copy-is-truly-unique/

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Wed, 8 Nov 2006 15:10:59 CST
Raw View
Andrei Polushin wrote:
> Douglas Gregor wrote:
> ?? Yes, concepts will greatly simplify the task of separate compilation
> of templates because they provide all neccessary type-checking.
> Concepts are predictable "interfaces" for template code, and the code
> that provides a predictable interface could be compiled separately.

Almost. The fact is that you can't get "perfect" type checking unless
you throw away or restrict specialization, and without perfect type
checking you can't do true separate compilation. Here's my favorite
example where specialization breaks the type-checking that concepts
provide:

  template<CopyConstructible T>
  void foo(vector<T>& vec) {
    T& x = vec.front();
  }

Does it type check? Yup.
Does it work for T = int? Yup.
Does it work for T = bool? Nope, vector<bool>'s front() method returns
a class type, not a bool&.

Here, we ended up getting a specialization at instantiation time that
didn't exactly have the same interface as the primary template. The
issue (along with ambiguity issues that crop up at instantiation time
with concepts) is covered in more depth in this paper:
  http://portal.acm.org/citation.cfm?id=1133981.1134014

> Thus the "export" problem could be solved, I don't know why you didn't
> mention that benefit in your proposal.
>
> BTW, by "separate compilation" I mean a compilation into some specific
> "byte code", not into binary objects.

Export might be easier to implement for constrained templates; I don't
really know. Actual separate compilation would be an interesting
research project, but there are significant challenges ahead.

> I would like to recall a better syntax, quoted at the beginning
> of this post:
>
>     // keyword "class" denotes constrained class
>     // keyword "typename" denotes another parameter
>
>     template <class T, typename U = T>
>     concept EqualityComparableTo {
>         bool operator==(U) = 0;    // member of T
>         bool U::operator==(T) = 0; // member of U - accepts T
>     }
>

Oh, that's subtle. Especially given that "T" in the example below
doesn't actually have to be a class.
Do you want to teach programmers that "class" and "typename" mean
exactly the same thing in templates, unless that template happens to be
a concept?

> Now the prevous question
>
> > We have qualified the second operator==, but rather than
> > being "T::U::operator==", as the first might suggest, it's just
> > "U::operator==". I find that nonuniformity rather unsatisfying.
>
> Could be resolved this way:
>
>     // parameter U is already constrained
>
>     template <class T, EqualityComparableTo<T> U = T>
>     concept EqualityComparableTo {
>         bool operator==(U) = 0;    // member of T
>     }
>
> Here we constrain exactly one type in each concept.

Sounds very limiting; see David Abrahams' examples.

> And I'm rather satisfied with that uniformity :)

Are you satisfied with the recursive constraint specification? "int" is
only EqualityComparableTo<long> if "long" is EqualityComparableTo<int>,
which only holds if "int" is EqualityComparableTo<long>...

> > Aside from concept maps and their use in adaptation of syntax, how does
> > that differ from the abstract base class:
> >
> >   class EqualityComparable {
> >     bool equals(const EqualityComparable&) = 0;
> >   };
>
> I would like the concepts as base classes, so I need no difference.

I have a hard time understanding how the "SameType" concept, which
states that its two type parameters are precisely the same type (not
subclasses of the same type), can work when concepts are base classes.

If A inherits SameType<B>, and A == B, then B inherits SameType<A>...
but, well, we have another recursive constraint, which is rather weird.
Also, if one then inherits A in a new subclass D, we can't have D
inherit SameType<B> (even though A inherits SameType<B>!), because D !=
B.

Note that if you can't handle SameType, you can't express the concept
constraints in the C++ Standard Library.

> There are useful applications of this feature: we can use them
> in place of CRTP, or use them to implement mix-in classes.

We already do CRTP and mix-in classes, and concepts support those uses.

  Cheers,
  Doug

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Wed, 8 Nov 2006 16:31:40 CST
Raw View
Douglas Gregor wrote:
>> The NOT constraint is rather more redundant: it is "used to direct
>> concept-based overloading", so that the overload *knows in advance*
>> that another overload exists. The result is identical once we have
>> the declarations of both overloads, so I think we don't need the
>> NOT constraint.
>
> There might not be an ordering among those two overloads based only on
> their normal constraints. It doesn't happen often, and it doesn't
> happen in the simple cases. The NOT constraint allows you to handle the
> hard cases where you truly have disjoint requirements in two overloads,
> but you need concept-based overloading to order them. unique_copy is a
> truly intruiging example where the C++ standard describes several
> variants of the algorithm that have some constraints that aren't
> ordered:
>
> http://conceptgcc.wordpress.com/2006/04/27/unique_copy-is-truly-unique/

I've found that we can write four overloads instead of three to resolve
the issue with unique_copy:

    // 1
    template<InputIterator          InIter,
             OutputIterator<
                InIter::value_type> OutIter>
      where <EqualityComparable     InIter::value_type,
             Assignable             InIter::value_type,
             CopyConstructible      InIter::value_type>
         // !ForwardIterator        InIter
         // !MutableForwardIterator OutIter
      OutIter unique_copy(InIter first, InIter last, OutIter result);

    // 2
    template<ForwardIterator        InIter,
             OutputIterator<
                InIter::value_type> OutIter>
      where <EqualityComparable     InIter::reference>
         // !MutableForwardIterator OutIter
      OutIter unique_copy(InIter first, InIter last, OutIter result);

    // 3
    template<InputIterator          InIter,
             MutableForwardIterator OutIter>
      where <EqualityComparable<
                InIter::value_type> OutIter::reference>
             Assignable<
                InIter::reference>  OutIter::reference>
         // !ForwardIterator        InIter
      OutIter unique_copy(InIter first, InIter last, OutIter result);

    // 4
    template<ForwardIterator        InIter,
             MutableForwardIterator OutIter>
      where <EqualityComparable     InIter::reference>
      OutIter unique_copy(InIter first, InIter last, OutIter result);

In short, it's like the following:

unique_copy(InputIterator,  InputIterator,  OutputIterator);
unique_copy(ForwardIterator,ForwardIterator,OutputIterator);
unique_copy(InputIterator,  InputIterator,  MutableForwardIterator);
unique_copy(ForwardIterator,ForwardIterator,MutableForwardIterator);

where the 4th overload resolves an ambiguity between 2nd and 3rd.

In general, 2 different requirements for each of N parameters will
require 2*N overloads. It's bad, but knowing in advance that another
overload exists is worse, IMHO.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Ganesh" <sgganesh@gmail.com>
Date: Thu, 2 Nov 2006 09:37:05 CST
Raw View
I was reading the paper "Concepts: Linguistic Support for Generic
Programming in C++". The idea is good, but I thought one can take an
alternative approach towards that.

Basically, one can think of the core idea in using type parameters in
templates as "duck typing": if the type looks like a type in the way
that is used in the template, then it can be substituted. So, I think,
an alternative way to look into the concepts is to simplify it much:

template <typename T ducks POD> {
          // use T in the way we use a POD type
}

If we want to enforce the some constraint on the way the type parameter
is used, it can be provided in the sample class. For example, if the
type T is expected to have a virtual destructor, then it can provided
in the ducks class and the compiler has to ensure that the
instantiating type has the same:

struct VirtualDestType {
          ~VirtualDestType () {}
};

template <typename T ducks VirtualDestType>
class Use {
          // use T in the way we use a Virtual dest type
          void foo() {
                  T * t = new U();  // say U is derived class of T
                   delete t;
                   // T has a virtual destructor, so ~U will be called

          }
}

struct Empty {};
struct NoMatch {
          ~NoMatch();
};
struct Match1  {
       virtual ~Match1();
};
struct Match2  {
       Match2() {}
       virtual ~Match2();
};

Use<Empty> u1; // compiler error, destructor not found while
substituting Empty for T
Use<NoMatch> u2; // compiler error, destructor is not virtual while
substituting NoMatch for T
Use<Match1> u3; // fine, the classes basic expectations match -  the
expected features look alike
Use<Match2> u4; // fine, the classes basic expectations match - the
expected features look alike, though Match2 has extra things

The core idea is this: Provide all the constraints that you want a
template parameter to follow in a sample class and say that the
template parameter just "ducks" the class thats given as constraint.
The advantage is that this is conceptually simple, only one more
keyword is required, doesn't break the backward compatibility and easy
to use and explain.

Thanks!
-Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]