Topic: Some remarks to N1848


Author: dsp@bdal.de (=?ISO-8859-1?Q?Daniel_Kr=FCgler?=)
Date: Tue, 6 Sep 2005 13:49:17 GMT
Raw View
Hello,

yesterday evening I started reading the "implementation-oriented"=20
concept paper of Gregor and Siek found at

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1848.pdf

I apologize in advance, if my following questions and remarks are=20
already answered in the follow-up article

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1849.pdf

which I havent't read yet (but will do in the following days).

1) The last para of ch. 2 says: "[..] These signatures must match
exactly." and the first para of 2.1 says similarly "[..] each model must
provide precisely the same types and functions as specified in the
concept [..]"

As example is given:

template<typename Iter>
concept RandomAccessIterator : BidirectionalIterator<Iter>
{
typename difference type; // associated type requirement
difference type operator-(Iter const&, Iter const&); // operation=20
requirement
};

template<typename T>
concept RandomAccessIterator<const T*>
{
typedef ptrdiff t difference type;
ptrdiff t operator-(const T* const& x, const T* const& y) { return x-y; }
};

Later on in 2.2.2 it is said: "[..] The required operations in a concept
are referred to as pseudosignatures, because while they are written as
concrete syntactic entities, they permit a much more loose matching of
syntax, allowing conversions and templates. This loose matching comes
into play when the implementations of an operation are generated for a
particular model."

Given the loose matching strategy, why is it necessary that models as
RandomAccessIterator<const T*> provide **exactly** matching functions?

Why would

ptrdiff t operator-(const T* x, const T* y) { return x-y; }

not suffice? (Note that compared to normal templates exact signature
matching of members in generic and specialized templates is not
required)

2)
- What is the difference between 'require' in 2.2.4 and the
well-documented 'where' clause? Is it just an artifact from earlier
versions of the proposal, an alias, or what else ??
- Are curly brackets mandatory for 'where' clauses or optional (see 1st
introductory example in ch. 2)?

3) An earlier proposal reused the keyword typeinfo instead of/additional
to typename in class/function templates and concepts. Has this been
removed?

4) Page 9 presents:

template<typename Iter>
where { MutableRandomAccessIterator<Iter>, LessThanComparable<value_type>=
 }
void sort(Iter first, Iter last);

Interestingly 'value_type' is unqualified in the where clause, how does
that come? Is it 'injected' by the leading=20
MutableRandomAccessIterator<Iter>?

So could we also swap participants in the where clause, such that e.g.

template<typename Iter>
where { LessThanComparable<value_type>, MutableRandomAccessIterator<Iter>=
 }
void sort(Iter first, Iter last);

would be fine? But is that correct concerning the unqualified
value_type?

Comparing with the latter text I would assume that

template<MutableRandomAccessIterator Iter>
where { LessThanComparable<value_type>}
void sort(Iter first, Iter last);

would be correct, is that right?

(A minor note: I assume, that the part "struct int {};" is an artifact
in this example - right?)

5) I don't understand, why the example in 4.1

template<typename T> where {} void f(const T& x, const T& y)
{
    x + y;
}

should produce a diagnostic, as described in the text. To my opinion the
empty where-clause implies "no constraints at all". So about what should
the compiler complain, because it does not know anything about the type
T at that point?

I would understand that

template<typename T> where {T =3D=3D X} void f(const T& x, const T& y)
{
    x + y;
}

produces a diagnostic, but that one was not given.

6) 4.4 discusses archetypes, the example given is

template<typename T>
concept Fooable
{
    int T::foo();
};

Question is: Can I express the signature of foo **partially**? Lets
consider that I don't want to limit the result type beside the fact
being EqualityComparable, that means

template<typename T, typename R>
concept Fooable
{
    R T::foo();
    where EqualComparable<R>;
};

OK, thats fine, but how can I use this information in the given example,
where R cannot be deduced at that point:

template<typename T, typename U >
   where {Fooable<T, ??>, T =3D=3D U}
bool bar(T& t, U& u)
{
    return t.foo() =3D=3D u.foo();
}

So what do we write? A first attempt would be to extend the function
template:

template<typename T, typename U, typename R >
   where {Fooable<T, R>, T =3D=3D U}
bool bar(T& t, U& u)
{
    return t.foo() =3D=3D u.foo();
}

but coming from classic C++ rules this would imply that we have to
specify R explicitly. Is that right? Is this problem solvable with the
proposal?

My assumption is, that the additional constraint "where
EqualComparable<R>" is not verifyable without specification of R, so I
went back to modify the original concept:

template<typename T>
concept Fooable
{
    ?? T::foo();
};

Can we make this valid somehow without being interested in the precise
return type of foo? Note that this kind of concept already exists in
current C++ where its said, that the return type is not used (e.g.
see allocator table 32 in 20.1.5, member functions deallocate, construct=20
and destroy).

Greetings from Bremen,

Daniel Kr=FCgler

---
[ 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.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: 7 Sep 2005 02:10:05 GMT
Raw View
Hello,

Daniel Kr   gler wrote:
> 1) The last para of ch. 2 says: "[..] These signatures must match
> exactly." and the first para of 2.1 says similarly "[..] each model must
> provide precisely the same types and functions as specified in the
> concept [..]"
>
> As example is given:
>
> template<typename Iter>
> concept RandomAccessIterator : BidirectionalIterator<Iter>
> {
> typename difference type; // associated type requirement
> difference type operator-(Iter const&, Iter const&); // operation
> requirement
> };
>
> template<typename T>
> concept RandomAccessIterator<const T*>
> {
> typedef ptrdiff t difference type;
> ptrdiff t operator-(const T* const& x, const T* const& y) { return x-y; }
> };
>
> Later on in 2.2.2 it is said: "[..] The required operations in a concept
> are referred to as pseudosignatures, because while they are written as
> concrete syntactic entities, they permit a much more loose matching of
> syntax, allowing conversions and templates. This loose matching comes
> into play when the implementations of an operation are generated for a
> particular model."

Ah, I see a problem with our presentation. We're using the word "match"
very loosely. In the first case (2 and 2.1), we're referring to the
need for declarations in the models and concepts to "match" precisely,
e.g., by the same modulo parameter names. This is needed so that a
function type-checked or compiled against a concept will work in
exactly the same way given any model.

The "loose matching" talked about in 2.2.2 is referring to the
*definition* of operations in models, which will allow conversions even
though the *declaration* matches exactly. "Loose matching" only occurs
when the compiler implicitly defines part of the model. Perhaps I can
clarify using your example:

> Given the loose matching strategy, why is it necessary that models as
> RandomAccessIterator<const T*> provide **exactly** matching functions?
>
> Why would
>
> ptrdiff t operator-(const T* x, const T* y) { return x-y; }
>
> not suffice? (Note that compared to normal templates exact signature
> matching of members in generic and specialized templates is not
> required)

This operator- declaration does not match the declaration in the
RandomAccessIterator concept because the parameters are passed by-value
instead of by const-reference. Imagine that we are compiling a generic
function that uses RandomAccessIterator<...>::operator-: because the
arguments are passed by reference in the concept, the compiler will
pass them by reference in the compiled version. If we then provide a
model that passes them by value, we run into problems: at best, it will
still work but we won't destruct the iterators properly. At worst,
we'll corrupt our stack and fail. Exact signatures mean that we can
separately type-check, so we know problems like this can't occur. There
are more subtle issues with reference binding and such that also
require that signatures match exactly, otherwise we won't be able to
guarantee that a fully type-checked template will instantiate properly.

The "loose matching" comes in when parts of a model are implicitly
generated. For instance, let's say we define part of a
RandomAccessIterator model for shared_ptrs that will use the
implicitly-generated operator-. First we show the operator- for
shared_ptrs:

  template<typename T>
  ptrdiff_t operator-(shared_ptr<T> x, shared_ptr<T> y); // #1

Now, we write the model for shared_ptrs, omitting operator-:

  template<typename T>
  concept RandomAccessIterator<shared_ptr<T> >
  {
    typename ptrdiff_t difference_type;
  };

The compiler implicitly generates:

  template<typename T>
  ptrdiff_t
  RandomAccessIterator<shared_ptr<T> >
  ::operator-(const shared_ptr<T>& x, const shared_ptr<T>& y)
  {
    return x - y; // finds #1
  }

Note that the signature of operator- exactly matches what the
RandomAccessIterator concept needs (so we can do separate
type-checking). However, inside the implementation of this generated
operator-, we end up calling #1, which involves some extra copies.

> 2)
> - What is the difference between 'require' in 2.2.4 and the
> well-documented 'where' clause? Is it just an artifact from earlier
> versions of the proposal, an alias, or what else ??

Good catch! The "require" is an artifact of earlier versions of the
proposal. It should be "where" now, as we've banished the "require"
keyword.

> - Are curly brackets mandatory for 'where' clauses or optional (see 1st
> introductory example in ch. 2)?

Curly brackets are mandatory for a where clause following a template
header (e.g., for a constrained function or class template); they're
not allowed within nested requirements (e.g., where "require" used to
be used). They should probably be optional in the latter context...

> 3) An earlier proposal reused the keyword typeinfo instead of/additional
> to typename in class/function templates and concepts. Has this been
> removed?

Yes, it has. The existence of requirements (in a where clause or using
the shortcut in the template header) now implies non-dependent
parameters. The '!' operator can be used to make a template parameter
unchecked/unsafe.

> 4) Page 9 presents:
>
> template<typename Iter>
> where { MutableRandomAccessIterator<Iter>, LessThanComparable<value_type> }
> void sort(Iter first, Iter last);
>
> Interestingly 'value_type' is unqualified in the where clause, how does
> that come? Is it 'injected' by the leading
> MutableRandomAccessIterator<Iter>?

Yes, it is. Associated types (and values, when we fully specify them)
can be found without qualification. N1849 has a section on name lookup
that gives a slightly more precise wording.

> So could we also swap participants in the where clause, such that e.g.
>
> template<typename Iter>
> where { LessThanComparable<value_type>, MutableRandomAccessIterator<Iter> }
> void sort(Iter first, Iter last);
>
> would be fine? But is that correct concerning the unqualified
> value_type?

That would fail to compile, because value_type is not in scope until
after "MutableRandomAccessIterator<Iter>" occurs. The same thing
happens with template parameters, e.g.,

  template<typename T, T value>
  struct integral_constant { ... };

> Comparing with the latter text I would assume that
>
> template<MutableRandomAccessIterator Iter>
> where { LessThanComparable<value_type>}
> void sort(Iter first, Iter last);
>
> would be correct, is that right?

Yes.

> (A minor note: I assume, that the part "struct int {};" is an artifact
> in this example - right?)

Oops! That's an artifact.

> 5) I don't understand, why the example in 4.1
>
> template<typename T> where {} void f(const T& x, const T& y)
> {
>     x + y;
> }
>
> should produce a diagnostic, as described in the text. To my opinion the
> empty where-clause implies "no constraints at all". So about what should
> the compiler complain, because it does not know anything about the type
> T at that point?

Normal "typename" parameters in C++ are quite a bit different from the
constrained "typename" parameters when there is a where clause. With
normal "typename" parameters, you can do anything to them in the
template, and it's up to the user to supply types with the right
syntax. With constrained parameters, the template can only do to the
type what it has required the user to provide. If you want to add to
T's, you need to tell the user to make T Addable. Since the user can
only instantiate your function with Addable T's, it will always
instantiate correctly. By writing an empty where clause, you are saying
that there are no requirements on T, i.e., T can be absolutely
anything. But, there are very few things you can do to *every* T out
there: you can't add them, for instance, because some types don't have
a + operator defined.

> I would understand that
>
> template<typename T> where {T == X} void f(const T& x, const T& y)
> {
>     x + y;
> }
>
> produces a diagnostic, but that one was not given.

Yes, this should also produce a diagnostic.

> 6) 4.4 discusses archetypes, the example given is
[snip]
> template<typename T, typename R>
> concept Fooable
> {
>     R T::foo();
>     where EqualComparable<R>;
> };
>
> OK, thats fine, but how can I use this information in the given example,
> where R cannot be deduced at that point:
>
> template<typename T, typename U >
>    where {Fooable<T, ??>, T == U}
> bool bar(T& t, U& u)
> {
>     return t.foo() == u.foo();
> }
[snip]
> but coming from classic C++ rules this would imply that we have to
> specify R explicitly. Is that right? Is this problem solvable with the
> proposal?

Yes, you are correct. This is solvable and is part of the motivation
for associated types. Let's turn R into an associated type, instead of
a parameter:

template<typename T>
concept Fooable
{
    typename R;
    R T::foo();
    where EqualComparable<R>;
};

Now we're saying that each Fooable<T> has an associated type R, that is
the return type of T::foo(). We know nothing about R, other than the
fact that it is CopyConstructible (because it can be returned) and
EqualComparable (because of the nested requirement). Now, we can go
back to your original function bar():

template<typename T, typename U >
   where {Fooable<T>, T == U}
bool bar(T& t, U& u)
{
    return t.foo() == u.foo();
}

That should type-check without any problems. t.foo() returns
Fooable<T>::R. Since T == T, u.foo() also returns Fooable<T>::R. Then,
since Fooable<T>::R is EqualComparable, the "==" operation type-checks
and everything works.

The iterator concepts do this kind of thing a *lot*, because they have
four associated types (value_type, reference, pointer,
difference_type). For instance, InputIterator says that
Convertible<reference, value_type>, so that one can take the result of
operator* and copy it into a value_type object.

Thanks for the great comments/questions. I look forward to your reading
of N1849 :)

  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.jamesd.demon.co.uk/csc/faq.html                       ]