Topic: Some remarks to N1849 (to add the truncated part)


Author: "Douglas Gregor" <doug.gregor@gmail.com>
Date: Sun, 18 Sep 2005 13:16:36 CST
Raw View
Hello,

There is an interesting theme that pervades my reply below; I'm
calling it out here because it'll come up later in the message, and
because I hope that we can catch a few more eyeballs because it is
*really* important for concepts.

There are two sides to concepts--type-checking templates and
determining whether a template can be instantiated--which are
inseparable. We can't change how one works without considering the
effect on the other. As C++ programmers accustomed to C++03 templates,
metaprogramming, and SFINAE, we tend to focus on constraining how the
user can use a template: When can it be instantiated? What will the
error messages look like? However, the more pervasive change that
concepts bring (and the more complicated part of the proposal to
reason about) is in the introduction of type-checking for
templates. Type-checking templates means that you can write your
templates and get feedback from the compiler immediately about their
correctness, so that instantiation-time errors can almost completely
disappear. Maintaining this type-checking is not trivial: for every
kind of requirement that goes into a concept or where clause, we need
to also specify how it is going to contribute to the type-checking of
templates. For instance, the seemingly innocent same-type constraints,
which are so trivial to check at instantiation time using type traits,
require tight integration with the compiler's type checker.

Daniel Kr   gler wrote:
> 5) 4.1.1: Why was the term "model-id" chosen instead of the more obvious
> term "concept-id"? The definition "A model-id is a template-id whose
> template-name refers to a concept." seems somewhat counterintuitive.
>
> Since the earlier proposed model keyword was cancelled, "concept-id"
> seems a more natural decision (for me ;-))

A concept is a set of requirements, whereas a model defines that (and
how) a particular set of types meet those requirements. They're
different kinds of entities, with different roles in the type
system. When we write CopyConstructible<T>, we're saying that there
exists (or we are assuming the existence of) a model
CopyConstructible<T>. In earlier proposals, we syntactically
distinguished "concept" and "model" to stress the difference. However,
for various reasons we had to drop the "model" keyword:

  1) It's too commonly used as an identifier, e.g., in the system
     headers on my Mac

  2) It's syntactically redundant (since "concept" works just as well
     to declare models)

  3) It's nonobvious that models are tied so tightly to concepts when
     they use different keywords.

We retained "model-id" to emphasize the separation between models and
concepts, even when the syntax doesn't separate the two. I think it's
the best of both worlds, really: the syntactic closeness can help
understand the relationship between concepts and models for the
purposes of model selection, partial ordering, and instantiation, but
using "model-id" emphasizes that there is a fundamental difference
deeper down.

> 6) Are language-linkage specifications part of pseudo-signatures?

You know, we never even considered that. I would say "no",
language-linkage specifications are not part of
pseudo-signatures. Pseudo-signatures are treated like inline static
member functions.

> 7) Pseudo-signatures and default arguments: According to 17.4.4.4
> library (non-virtual) member functions may use alternative signatures
> with default-values or may chose to use multiple functions w/o
> default-values instead of one with. Can concepts handle this looseness
> requirement?

Yes, when the pseudo-signature is implicitly generated. For instance:

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

  struct X {
    void foo(int y, double z = 3.14159);
  };

  template<>
  concept Foo<X> {}; // OK!

Within the model, the compiler implicitly generates the following (we
show member function requirements as non-members):

  concept void Foo<X>::foo(X& x, int y)
  {
    x.foo(y);
  }

The implicitly-generated "foo" for Foo<X> matches the signature in
concept "Foo", but the implementation just performs a call to the
underlying member function, which can fill in default parameters.

> 8) Typetraits and (T' == T''): It is argued in 3.3.4. that is_same would
> be bad in where clauses because it would demand nontrivial
> implementation efford. Does this reasoning apply for **all** kinds of
> type traits usages in where clauses? I assume it applies, although I see
> currently a hole here, since I can use is_same<T', T''>::value as
> ice-requirement.

We were trying to argue something a bit different, but I'll get back
to that in a minute...

You can go ahead and use type traits as ice-requirements in a where
clause, but is_same<T', T''>::value is *not* the same as T' ==
T''. ice requirements can't contribute anything to the type checking
of the template itself. For instance, consider the following:

  template<typename T, typename U> where {T == U}
  bool foo(T* x, U* y)
  {
    return x == y;
  }

That's okay, because T == U implies T* == U*, and the compiler knows
to apply the built-in == operator for pointers. Now, write the same
thing using an is_same type trait:

  template<typename T, typename U> where { is_same<T, U>::value }
  bool foo(T* x, U* y)
  {
    return x == y;
  }

>From the perspective of the caller of "foo", the result is the same:
you can only call foo when the types T and U are the same. However,
the body of "foo" needs to be able to type-check against the where
clause. There is no built-in operator== that takes two pointers to
different value types, so the compiler needs to somehow prove that T
== U to type-check this routine. Doing this would require digging into
the known specializations of is_same and from there determining that
the requirement is_same<T, U>::value actually means that T == U are
the same. You can hack a compiler to handle this special case, by
detecting the underlying pattern used by is_same (and then turning it
into, essentially, a same-type constraint), but in general we can't
deduce anything meaningful from a set of specializations.

So, in 3.3.4 we argue that Same cannot be a regular concept without
support from the compiler. Same-type constraints affect type equality
within the compiler's type-checker, requiring changes directly to the
compiler that cannot be faked using type traits or regular concepts.

> 9) Is it possible to define a (general) model incompletely compared to
> its concept, but use specialized models to fill the missing gaps?
> Example:
>
> template <typename T>
> concept Laughable {
>      void giggle(T);
>      struct Volume;
> };

Just FYI, we don't support structs in concepts. I'll assume instead
that the above contains "typename Volume;".

> // Partially define above concept with the following model:
> template <typename T>
>      where { IsArithmetic<T>} // or concept Laughable<is_arithmetic<T> > ??
> concept Laughable<T>
> {
>      void giggle(T) { std::cout << typeid(T).name(); }
>      // Note: No Volume defined!!
> };

This model template is ill-formed according to our proposal. The
compiler will produce a diagnostic stating that the requirement for an
associated type "Volume" has not been fulfilled by the model.

> Note that this idea is somewhat related to the proposed behaviour of
> **refined** models, but is not the same.

If we allowed this behavior, we would lose separate type checking. The
concept Laughable guarantees that there is a function giggle and a
type Volume, and a generic function honk() using Laughable can rely on
those guarantees. If we were allowed to declare a model of Laughable
that didn't meet those requirements, we run into problems: it would be
okay to call honk() with that model, but the instantiation of honk()
would cause errors if it used the type Volume.

> 10) According to C++ (and C AFAIK) enumerations cannot be declared w/o
> proper definition. Is this restriction relaxed for concepts, e.g. may I
> write:
>
> template <typename T>
> concept Worry {
>      enum Whatever; // OK??
> };

Enums are not allowed in concepts.

> (As you see, I'm going to approach to value inside concepts, again ;-))

I noticed :)

> 11) 4.2.4 presents:
>
> model B<int> {
>      int zero() { return 0; }
> };
>
> Is that wellformed since it seems that the previously proposed keyword
> "model" does no longer exist? Or should have this been:
>
> template <>
> concept B<int> {
>      int zero() { return 0; }
> };
>
> ?

The latter is correct; the "model" keyword no longer exists, and we'll
fix
the example in the next revision. Thanks!

> 12) After reading 4.2.6 I realized an interesting problem: Do concept
> checks assume public access to the corresponding types and
> pseudo-functions? What about virtual functions, which are often provided
> as protected (or even private) interface functions. Consider:
>
> template <typename T, typename S>
> concept VirtualReadable {
>      virtual void T::do_read(const S& src);
> };
>
> class Reader {
> public:
>      virtual ~Reader();
>      void read(int value) { do_read(value); }
> protected:
>      virtual void do_read(int);
> };
>
> template<>
> concept VirtualReadable<Reader, int> {}; // OK??

This code is ill-formed. Concepts and models do not have any special
access. If a model needs to access private or protected members of a
class, you can write a friend declaration for a model (which looks
just like a friend declaration for a class).

> Note that this point is not related to the problem whether the concept
> itself has access to the Reader::do_read member (at least this is not
> the point I would like to refer to), but the question whether
> the concepts can also handle situations like these where the required
> interface is not necessary publicly existent?

We don't allow concepts/models any special access because it would
break encapsulation.

> 13) Declarations and definitions of class/function templates: I assume
> that where clauses must be completely replicated for all declarations of
> a corresponding entity?

Yes.

> 14) 4.3.2: Why does the grammar not include
>
> type-specifier != type-specifier
>
> ? This extension would it make much easier to realize SFINAE-like
> mutually exclusive function templates as in:
>
> template <typename T, typename U>
>      where {T == U}
> void bar(T, U);
>
> template <typename T, typename U>
>      where {T != U}
> void bar(T, U);
>
> I strongly recommend its addition, which is as natural as the existence
> of both operator== and operator!= in the EqualityComparable concept due
> to the symmetry relation between == and !=. Just writing !(T == U) seems
> unnecessary cumbersome.

We omitted this feature because != cannot be used for type
checking. Two types T and U are already assumed by the type-checker to
be distinct unless proven equal (e.g., because of same-type
constraints). I'm not convinced that it's even all that useful. For
instance, your two bar overloads can be written as:

template<typename T, typename U> where { T == U } void bar(T, U); // #1
template<typename T, typename U> where {} void bar(T, U); // #2

#1 is more specific than #2. If T and U are indeed equal in a call to
 bar(), #1 will be picked; otherwise, #2 will be picked.

> 15) Table 4.3: The item "id-expression" used in the last of the
> "type-parameter" items should be set in italics to mark it as proper
> grammar-item name.
>
> Table 4.1.3: Both "typename" items should be set in courier instead of
> italics, because "typename" is a keyword and not a grammar element here.

Thanks!

> 16) 4.4.1: I propose to move the footnote 4 of page 27 to the first
> compiler output of page 26 (or at least a link to this foot-note),
> because my immediate reaction was to send a bug request at this place
> concerning the contradictory behaviour compared to the assertions made
> in 4.3.4.

Yes, that's a good idea. Actually, we've implemented constraint
propagation in the latest version of the compiler, so we can just
correct the text and output.

> 17) Page 29, last paragraph, says "#1 and #3 are implied
> specializations, so they will enter the overload set."
>
> Why on earth should #1 be ever considered as specializations in case of
> the presented lower bound() , because the first step already had chosen
> #2, which was the minimum acceptable iteratory category in this case?

Oops, you're right! #1 is not considered at all; I'll change the
requirement on Iter to be ForwardIterator.

> 18) The two examples on p. 30 (before 4.4.3) with class template foo
> should either chose struct instead of class or should declare foo::bar
> in the public part of foo, otherwise the access in f() would not be
> feasible.

Yep, you're right!

> Shouldn't have function template g() at least an empty where
> clause to fit to the description "[..] the definition of g() in
> the program below will type check based [..]"?

Yes, there should be an empty where clause on g(). Thanks!

> 19) Proposed syntax for associated values - what about the following:
>
> template <typename T>
> concept A {
>      static const bool value; // Note: associated values require known or
> dependent type
>      static const int c = 123; // Default value provided by ice-expression
>      where c > 0; // Constraints on ice-compatible associated values.
>      static const char d = T::chr; // Default value provided by T
>      static T non_type; // similar to template non-type parameter - useful?
> };

Everything there seems reasonable to me. Of course, "where c > 0"
constrains models but does not contribute to type-checking.

> template<>
> concept A<int> {
>      static const bool value = true;
>      static const char d = 'i';
>      static int non_type;
> };
>
> template<>
> int A<int>::non_type = 42; // Definition!

I think we should require in-model definitions for associated values,
so they are guaranteed to be integral constant expressions and can be
used as template arguments.

> struct MyType {
>      enum {value = true}; // Is OK, because it's convertible to bool
> according to promotion rules(?)
>      static const char chr = 'M'; // used for definition of A<MyType>::d
>      static MyType non_type;
> };
>
> MyType MyType::non_type; // Usual definition
>
> template<>
> concept A<MyType> {}; // Completely defined!

How does A<MyType>::value get its value?

> Remarks: Only static values are allowed to prevent problems with non-class
> types.

Agreed.


> 20) 4.5.5: The example function equiv does actually **not** use the
> features from the exported functions, because it only uses the
> operator<(), which is already provided by the type X itself.

You're right, of course.

> Proposed
> change:
>
> bool equiv(X x, X y) {
>      return (x >= y) && (x <= y); // Ok!
> }

Yep, looks good!

> 21) A2: struct concept Arrowable is defined as
>
> template<typename Ptrlike, typename Value>
> struct concept Arrowable
> {
>      typename arrow result = Value*;
>      arrow result operator->(Ptrlike);
> };
>
> Does that mean, that all Ptrlike entities must be CopyConstructible?
> What about auto_ptr<>? (Note that I distinguish here the necessary
> CopyConstructible requirements on iterators from the more general
> Arrowable concept itself)

Yes, it does mean that, although it shouldn't. Arrowable is a
horrible, horrible mess; we haven't really decided how best to handle
it, yet.

> 22) A5:
> -Typo in function template copy: Argument 'first1' should be named
> 'first', otherwise its implementation is illformed.
> - Typo in 2nd function template transform: Replace 'Func' identifier in
> where clause with 'BinOp'

Okay.

> So that's enough now!

Thanks!

  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                       ]





Author: dsp@bdal.de (=?ISO-8859-1?Q?Daniel_Kr=FCgler?=)
Date: Sat, 17 Sep 2005 02:01:05 GMT
Raw View
{Note: this is **not a repetition** of my previous posting
"Some remarks to N1849 (2nd trial)", but here I try to
attach those parts which where obviously truncated. Since this
time is the second one I observe truncation of news group
contributions (The first one was rejected by the moderator),
I would like to understand, due to which reasons those truncations
could happen? Active sun spots ;-))}

Here follows the remainder of my previous posting in the hope
that it will come trohgh completely (The last point should have the=20
number 22):

[..]

5) 4.1.1: Why was the term "model-id" chosen instead of the more obvious
term "concept-id"? The definition "A model-id is a template-id whose
template-name refers to a concept." seems somewhat counterintuitive.

Since the earlier proposed model keyword was cancelled, "concept-id"
seems a more natural decision (for me ;-))

6) Are language-linkage specifications part of pseudo-signatures?

7) Pseudo-signatures and default arguments: According to 17.4.4.4
library (non-virtual) member functions may use alternative signatures
with default-values or may chose to use multiple functions w/o
default-values instead of one with. Can concepts handle this looseness
requirement?

8) Typetraits and (T' =3D=3D T''): It is argued in 3.3.4. that is_same wo=
uld
be bad in where clauses because it would demand nontrivial
implementation efford. Does this reasoning apply for **all** kinds of
type traits usages in where clauses? I assume it applies, although I see
currently a hole here, since I can use is_same<T', T''>::value as
ice-requirement.

9) Is it possible to define a (general) model incompletely compared to
its concept, but use specialized models to fill the missing gaps?
Example:

template <typename T>
concept Laughable {
     void giggle(T);
     struct Volume;
};

// Partially define above concept with the following model:
template <typename T>
     where { IsArithmetic<T>} // or concept Laughable<is_arithmetic<T> > =
??
concept Laughable<T>
{
     void giggle(T) { std::cout << typeid(T).name(); }
     // Note: No Volume defined!!
};

// Completing the partially defined model for int:
template <>
concept Laughable<int> { // int fulfills is_arithmetic<>
     struct Volume {  enum {value =3D 1}; };
};

// Completing the partially defined model for float:
template <>
concept Laughable<float> { // float fulfills is_arithmetic<>
     struct Volume {  enum {value =3D -1}; };
};

Note that this idea is somewhat related to the proposed behaviour of
**refined** models, but is not the same.

10) According to C++ (and C AFAIK) enumerations cannot be declared w/o
proper definition. Is this restriction relaxed for concepts, e.g. may I
write:

template <typename T>
concept Worry {
     enum Whatever; // OK??
};

(As you see, I'm going to approach to value inside concepts, again ;-))

11) 4.2.4 presents:

model B<int> {
     int zero() { return 0; }
};

Is that wellformed since it seems that the previously proposed keyword
"model" does no longer exist? Or should have this been:

template <>
concept B<int> {
     int zero() { return 0; }
};

?

12) After reading 4.2.6 I realized an interesting problem: Do concept
checks assume public access to the corresponding types and
pseudo-functions? What about virtual functions, which are often provided
as protected (or even private) interface functions. Consider:

template <typename T, typename S>
concept VirtualReadable {
     virtual void T::do_read(const S& src);
};

class Reader {
public:
     virtual ~Reader();
     void read(int value) { do_read(value); }
protected:
     virtual void do_read(int);
};

template<>
concept VirtualReadable<Reader, int> {}; // OK??

Note that this point is not related to the problem whether the concept
itself has access to the Reader::do_read member (at least this is not
the point I would like to refer to), but the question whether
the concepts can also handle situations like these where the required
interface is not necessary publicly existent?

13) Declarations and definitions of class/function templates: I assume
that where clauses must be completely replicated for all declarations of
a corresponding entity?

14) 4.3.2: Why does the grammar not include

type-specifier !=3D type-specifier

? This extension would it make much easier to realize SFINAE-like
mutually exclusive function templates as in:

template <typename T, typename U>
     where {T =3D=3D U}
void bar(T, U);

template <typename T, typename U>
     where {T !=3D U}
void bar(T, U);

I strongly recommend its addition, which is as natural as the existence
of both operator=3D=3D and operator!=3D in the EqualityComparable concept=
 due
to the symmetry relation between =3D=3D and !=3D. Just writing !(T =3D=3D=
 U) seems
unnecessary cumbersome.

15) Table 4.3: The item "id-expression" used in the last of the
"type-parameter" items should be set in italics to mark it as proper
grammar-item name.

Table 4.1.3: Both "typename" items should be set in courier instead of
italics, because "typename" is a keyword and not a grammar element here.

16) 4.4.1: I propose to move the footnote 4 of page 27 to the first
compiler output of page 26 (or at least a link to this foot-note),
because my immediate reaction was to send a bug request at this place
concerning the contradictory behaviour compared to the assertions made
in 4.3.4.

17) Page 29, last paragraph, says "#1 and #3 are implied
specializations, so they will enter the overload set."

Why on earth should #1 be ever considered as specializations in case of
the presented lower bound() , because the first step already had chosen
#2, which was the minimum acceptable iteratory category in this case?

18) The two examples on p. 30 (before 4.4.3) with class template foo
should either chose struct instead of class or should declare foo::bar
in the public part of foo, otherwise the access in f() would not be
feasible. Shouldn't have function template g() at least an empty where
clause to fit to the description "[..] the definition of g() in
the program below will type check based [..]"?

19) Proposed syntax for associated values - what about the following:

template <typename T>
concept A {
     static const bool value; // Note: associated values require known or
dependent type
     static const int c =3D 123; // Default value provided by ice-express=
ion
     where c > 0; // Constraints on ice-compatible associated values.
     static const char d =3D T::chr; // Default value provided by T
     static T non_type; // similar to template non-type parameter - usefu=
l?
};

template<>
concept A<int> {
     static const bool value =3D true;
     static const char d =3D 'i';
     static int non_type;
};

template<>
int A<int>::non_type =3D 42; // Definition!

template<>
concept A<double> {
     static const bool value =3D false;
     static const char d =3D 'd';
     static double non_type;
};

template<>
double A<double>::non_type; // Definition!

struct MyType {
     enum {value =3D true}; // Is OK, because it's convertible to bool
according to promotion rules(?)
     static const char chr =3D 'M'; // used for definition of A<MyType>::=
d
     static MyType non_type;
};

MyType MyType::non_type; // Usual definition

template<>
concept A<MyType> {}; // Completely defined!

Remarks: Only static values are allowed to prevent problems with non-clas=
s
types.

Example of usage:

template<bool Flag, typename T>
    where { A<T>, Flag =3D=3D value || (d =3D=3D 'e' && c - 500 < 0 )} //=
 value,c,
and d injected by A<T>
T foo(T) {
      return non_type;
}

20) 4.5.5: The example function equiv does actually **not** use the
features from the exported functions, because it only uses the
operator<(), which is already provided by the type X itself. Proposed
change:

bool equiv(X x, X y) {
     return (x >=3D y) && (x <=3D y); // Ok!
}

21) A2: struct concept Arrowable is defined as

template<typename Ptrlike, typename Value>
struct concept Arrowable
{
     typename arrow result =3D Value*;
     arrow result operator->(Ptrlike);
};

Does that mean, that all Ptrlike entities must be CopyConstructible?
What about auto_ptr<>? (Note that I distinguish here the necessary
CopyConstructible requirements on iterators from the more general
Arrowable concept itself)

22) A5:
-Typo in function template copy: Argument 'first1' should be named
'first', otherwise its implementation is illformed.
- Typo in 2nd function template transform: Replace 'Func' identifier in
where clause with 'BinOp'

So that's enough now!

Good luck for the proposal and many 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                       ]