Topic: Pre-proposal: user-defined error messages


Author: "Sergey P. Derevyago" <non-existent@iobox.com>
Date: Tue, 10 Apr 2001 14:45:18 GMT
Raw View
Christopher Eltschka wrote:
> My point is: What is the _semantics_ which your AssignableConcept
> is supposed to describe?
 You _can_ compile the code which operates with the given parameters (x, y,
double).

> I.e.: Why should I want to make a definition like you showed?
 To issue the early diagnostics.

> And especially: What's better in your definition than in mine?
 It's much more generic. In particular, I can _exactly_ express your

concept Assignable(Assignable& a, Assignable const& b)
{
  a = b;
  Assignable c(b);
}

with all (implicit) limitations through the

 concept AssignableConcept(x, y, typename Assignable)
 {
   Assignable& a=x;
   Assignable const& b=y;

   a = b;
   Assignable c(b);
 }

> If you can't show me that, I'll keep my conclusion that your
> proposal is inferior to mine. Simply because mine allows all
> semantics I want to express to be expressed (including semantics
> which your proposal doesn't allow to be expressed - f.ex.
> the Assignable concept excluding auto_ptr-like clases).
 Surely, you're not right.

> > > Well, just like macros are an inferiour solution than templates,
> >         But not for all problems. Current templates have some "wellknown"
> > limitations.
> OK, what problem does your proposal solve which mine doesn't?
 Consider the following code:

concept CanCopyConcept(y, typename Y)
{
 Y z(y);
}

class X {
      X(const X&);  // private by default
      friend void f(X&);
      // ...
};

void f(X& x)
{
 CanCopyConcept(x, X);  // this works only because of f() is a friend of X
}

--
         With all respect, Sergey.          http://cpp3.virtualave.net/
         mailto : ders at skeptik.net

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: Christopher Eltschka <celtschk@dollywood.itp.tuwien.ac.at>
Date: Tue, 17 Apr 2001 22:50:03 GMT
Raw View
"Sergey P. Derevyago" wrote:
>
> Christopher Eltschka wrote:
> > My point is: What is the _semantics_ which your AssignableConcept
> > is supposed to describe?
>         You _can_ compile the code which operates with the given parameters (x, y,
> double).

And if I show you a program, and you ask me "what does the program
do?" and tell you "it executes the statements in the order dictated
by the control sataements", would you be satisfied?
I guess not.

So what is the abstraction this concept describes?

>
> > I.e.: Why should I want to make a definition like you showed?
>         To issue the early diagnostics.

Of course. And what does the diagnostic tell me? (And please don't
say: "That the code didn't compile". That I know myself.)

>
> > And especially: What's better in your definition than in mine?
>         It's much more generic. In particular, I can _exactly_ express your
>
> concept Assignable(Assignable& a, Assignable const& b)
> {
>   a = b;
>   Assignable c(b);
> }
>
> with all (implicit) limitations through the
>
>  concept AssignableConcept(x, y, typename Assignable)
>  {
>    Assignable& a=x;
>    Assignable const& b=y;
>
>    a = b;
>    Assignable c(b);
>  }

This does *not* say the same.
First, it can be used to check that x can be bound to a reference
of a _different_ type. So the following would be closer to my
definition (assuming we would have typeof):

concept AssignableConcept(x, y)
{
  typedef typeof(x) Assignable;
  Assignable& a = x;
  Assignable const& b = y;

  a = b;
  Assignable c(b);
}

But that's not really the point. If you look at your definition,
it tells you if for _specific variables_ it works. But to be
assignable is *not* a property of a variable, it's a property
of a type. If I can assign x to y, then I can assign _any_
variable of the same type as x to _any_ variable of the same
type as y. So the following would be even closer to my definition:

concept AssignableConcept(typename Assignable)
{
  Assignable& a = (Some assignable coming out of the air);
  Assignable const& b = (Sone assignable coming out of the air);

  a = b;
  Assignable c(b);
}

Those "coming out of the air" objects are the reason why
I introduced "function parameters" (and this in turn is
why I used a function-like syntax): By making them fake
function parameters, I don't have to declare where they
come from. It's like saying: "Assuming we have a modifiable
object named "a" and a possibly const object named "b",
both of a type conforming to Assignable, then the following
constructions shall compile, where Assignable denotes the
type during assignment."

But even now, this is not quite the same as my proposal,
since there's one important thing missing which you cannot
provide: I can _declare_ a type to be Assignable (indeed,
one point is that I can even _require_ types to be explicit
declared conforming to a concept to get them conforming to
a concept). This means, I don't just have a compile-time
assertion, but a full-blown interface. Indeed, I used this
fact to invent overloading on concepts (which conceptionally
is already done for iterators in STL, except that it uses
a big machinery which takes some time to understand in itself,
and the compiler does not ensure that I didn't tell him
something wrong, unless the final template uses a feature I
failed to provide, or which I got wrong).

Your "compile time assertion" doesn't allow that (assertions
cannot do that in princpile).

The difference is that your concept is just a simple solution
to the question "how do I get error messages if some code
wouldn't compile, even if I don't want to execute the code".
My concept is a complete interface layer on top of types,
and as a side effect solves that question, and as another
side effect allows an easy extension to add explicit language
support for "overloading on concept". The latter is something
which is obviously needed (the STL iterators use a big,
complicates machinery to get this effect), but is not
currently supported by the language (this is why the machinery
is so big).

>
> > If you can't show me that, I'll keep my conclusion that your
> > proposal is inferior to mine. Simply because mine allows all
> > semantics I want to express to be expressed (including semantics
> > which your proposal doesn't allow to be expressed - f.ex.
> > the Assignable concept excluding auto_ptr-like clases).
>         Surely, you're not right.

Ah, so you can read my mind to find out which semantics I want
to express? ;-)

>
> > > > Well, just like macros are an inferiour solution than templates,
> > >         But not for all problems. Current templates have some "wellknown"
> > > limitations.
> > OK, what problem does your proposal solve which mine doesn't?
>         Consider the following code:
>
> concept CanCopyConcept(y, typename Y)
> {
>  Y z(y);
> }
>
> class X {
>       X(const X&);  // private by default
>       friend void f(X&);
>       // ...
> };
>
> void f(X& x)
> {
>  CanCopyConcept(x, X);  // this works only because of f() is a friend of X
> }


Since I didn't make specifications about friendship at all,
from my previous texts you cannot conclude that this would not
be possible with my proposal.

For example:

concept CanCopyConcept(CanCopyConcept& y)
{
  CanCopyConcept z(y);
}

class X
{
  X(const x&);
  friend void f(X&);
  // ...
};

void f(X& x)
{
  CanCopyConcept X;
    // local declaration of X to conform to CanCopyConcept
}

I've explicitly used local scoped conformity declarations,
but I never said anything about how access rights should be
handled (indeed, I even didn't think about that). Therefore
it would well be possible to define that local declarations
obey the local access rights (i.e. for the local declaration
above, no error is generated, sice f is friend of X, and
therefore X can be copied).

Of course this poses the question how the following
should be treated:

template<CanCopyConcept T> void foo(T& t);

void f(X& x)
{
  CanCopyConcept X;
    // declare X conforming - assuming the rule above

  foo(x);
    // oops: foo is no friend of X, therefore this call
    // should _not_ be allowed.
}

Maybe a solution for local-scoped conformity would be that
explicit local conformity specifications are only in effect
locally, but calling of functions outside the local scope
where the conformity is declared needs the check to be
re-done for the called function. However, the inference
is done implicitly even if it is normally required to be
explicit (because, after all, an explicit conformance
statement has been seen), so in effect one cannot see the
difference unless the type doesn't conform in the outer
scope.

Another point is that foo might itself be a friend of X.
In this case, IMHO using X as template parameter should be
allowed, even from a scope where X isn't copyable (after all,
inside foo it _is_ copyable).

Seems quite a good solution to me. Thanks for the example.

But you couldn't make a point about this, since I didn't
specify it - and as it has turned out, it's possible
(and IMHO sensible) to specify it in a way that your example
can be expressed in my concept as well. So saying it wouldn't
be possible with my concept was wrong. It just wasn't yet
specified.

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: Christopher Eltschka <celtschk@dollywood.itp.tuwien.ac.at>
Date: Tue, 3 Apr 2001 18:43:00 GMT
Raw View
"Brian McNamara!" wrote:
>
> As people write more and more heavily templated code and template
> meta-programs, I've seen a lot of cases where programmers specifically
> inject constructs that will force a template instantiation not to
> compile.  Sometimes they're simple compile-time assertions, other times
> they're more complicated, such as the "concept checks" implemented in
> SGI's STL.
>
> The one thing these all have in common is that while they get the job
> done (enforce that programs with certain bad template instantiations
> fail to compile), the diagnostics they evoke from the compiler tend to
> look horrible.  For instance, one compile-time assertion implementation
> I've seen mentioned on comp.lang.c++.moderated ensures that
> CT_Assert<false> will fail to compile because it tries to create an
> array of zero (or negative one) elements.  The error messages from
> different compilers vary only in just _how_ugly_ the diagnostics they
> produce are.
>
> I would like to propose the following extension.  The code
>
>    Error<true,"some string">();
>
> should render a program uncompilable, whereas
>
>    Error<false,"some string">();
>
> is a harmless no-op.  Furthermore, in the former case, the
> implementation should issue a diagnostic along the lines of
>
>    User-defined error: "some string"
>
> Here is a motivating example.  I want certain instantiations of f() not
> to compile (see the code for details).  Currently, to get literate
> diagnostics, I go through various contortions like this:
>
>    template <class T> struct Error {};
>
>    template <class T, class U, bool b> struct f_error {};
>    template <class T, class U> struct f_error<T,U,false> {
>       typename Error<T>::T_must_be_smaller_than_U x; // line 5
>    };
>
>    template <class T, class U>
>    void f() {
>       // for some reason, we need to ensure that sizeof(T) < sizeof(U)
>       f_error<T, U, sizeof(T) < sizeof(U)>();   // line 11
>    }
>
>    int main() {
>       f<char,int>();   // ok
>       f<int,char>();   // bad                   // line 16
>    }
>
> The latest g++ gives a decent error:
>
>    err2.cc: In instantiation of `f_error<int, char, false>':
>    err2.cc:11:   instantiated from `void f() [with T = int, U = char]'
>    err2.cc:16:   instantiated from here
>    err2.cc:5: no type named `T_must_be_smaller_than_U' in `struct Error<int>'
>
> However I only know this will create a decent error message for my
> version of g++; another compiler may ignore all my work to get the text
> "T_must_be_smaller_than_U" into my error message.  I would like to be
> able to just do this:
>
>    template <class T, class U>
>    void f() {
>       // for some reason, we need to ensure that sizeof(T) < sizeof(U)
>       Error<!(sizeof(T) < sizeof(U)), "T must be smaller than U">();
>    }
>
>    int main() {
>       f<char,int>();   // ok
>       f<int,char>();   // bad      // line 9
>    }
>
> and have my compiler say
>
>    err2.cc: In instantiation of `void f() [with T = int, U = char]'
>    err2.cc:9:   instantiated from here
>    err2.cc:4: User-defined error: "T must be smaller than U"
>
> I picked the syntax of Error<condition,"text">(); because it looks like
> it could have been a real entity declared as
>
>    template <bool, char *> struct Error {};
>
> In other words, I think it doesn't require modifying the grammar to
> implement the extension.  Furthermore, since string literals don't have
> external linkage, they're not valid template parameters, so this
> extension doesn't supplant any valid code.  Of course, an implementor
> would probably want to name it __Error so as not to pollute the user
> namespace.
>
> Similarly, __Warning could provide a diagnostic but still allow the
> program to compile.
>
> I am interested to know if any implementors have such an extension.  I
> think it might be an easy one to add, and it would really be a
> _tremendous_ help to those of us doing lots of crazy template stuff.
> My goal with this 'pre-proposal' is to generate some discussion, and
> hopefully motivate some vendors to supply this extension, so we can
> have some "existing practice" to work with for a while in the hopes of
> standardizing in the future.
>
> So tell me what you think of the general idea, and let's get crackin'.
> :)

Well, I think there should be a more general mechanism.
The point is that those checks generally are checks that types
are models of some given concept. Therefore we should have
a way to describe a concept, and declare that a template type
must implement that concept.

Now the power of templates partly lies in the fact that you don't
need to declare that a given type fulfils a given interface
when defining the type; this way you can use algorithms on types
which were written before you had even the idea that this concept
would sometimes be useful, provided the interface fulfills it.
For example, if your algorithm needs the type to describe a
semi-group, you are not stuck just because the author of the type
declared it a group, but didn't define semigroup at all; you just
know that every group is a semigroup, and therefore you can just
use the type on your algorithm.

OTOH explicitly stating that a class conforms to an interface
_does_ have an advantage; the compiler can check it for you.
Indeed, this is what the template checking tricks do.
Now, on templates, the compiler checking is done by allowed
operations: The compiler checks f. ex. that a = b + c is allowed.
That is, the compiler checkable parts of a concept consist of
compileable code. If the code is compileable, the compiler
accepts the type, otherwise it doesn't. The point of the
template tricks now is to allow the compiler to check code
without actually compiling it into machine code (or at least
without executing it). This is IMHO what should be formalized.

Now my suggestion is the following:

1. Introduce a new keyword "concept" (or maybe __concept),
   which allows to define a concept "by example": The syntax
   is basically the same as for a function, except that you
   have the keyword "concept" in front, and no return type.

Example:

concept Assignable(Assignable& a, Assignable const& b)
{
  a = b;
  Assignable c(b);
}

This defines the concept Assignable. A type conforms to
the concept Assignable, if the test function

template<class Assignable>
 void __test_concept(Assignable& a, Assignable const& b)
{
  a = b;
  Assignable c(b);
}

would compile. However, no code is ever generated for this.

In addition, template concepts could be allowed. This would
also allow to define relations as concepts.

Example:

template<class Other>
 concept ComparableWith(ComparableWith const& a, Other const& b)
{
  a == b; b == a; a < b; b < a; a > b; b > a; a <= b; b <= a;
  a >= b; b >= a; a != b; b != a;
}


2. Allow types to be declared conforming to a concept, but
   _after_ they have been defined. The compiler would then
   check that this type conforms to the concept, and give
   an error message if it doesn't. This would also allow
   "inheritance" for concepts. Example:

concept InputIterator(InputIterator& iter)
{
  typedef typename std::iterator_traits<InputIterator>::value_type
value_type;
    // remember, InputIterator behaves like a template parameter here!

  Assignable InputIterator;
    // Input iterators must be assigneable

  value_type v = *iter;
  ++iter; iter++;
  v = *iter++;
}

Assignable double;
  // explicitly tell the compiler that double is (believed to be)
assignable

ComparableWith<float> double;
  // explicitly tell the compiler that double and float are comparable

template<class T> InputIterator std::istream_iterator<T>;
  // tell the compilers that all instances of std::istream_iterator<T>
  // are input iterators


3. Allow templates to be restricted to certain concepts. There should
   ideally be two ways: The strict way (which demands that the type
   has to be declared conforming explicitly), and the loose way (which
   effectively is what templates with compile time checks do today).

Example:

template<class T: Assignable> // loose checking
 void assign(T& a, T const& b)
{
  a = b;
}

template<Assignable T> // strict checking
 void swap(T& a, T& b)
{
  T tmp = a;
  a = b;
  b = tmp;
}

int main()
{
  int a, b;
  assign(a, b); // OK: ints conform to Assignable
  swap(a, b);   // Error: int was not declared to be an Assignable

  Assigneable int; // explicitly declare int to be an Assigneble
  swap(a, b);   // OK

  int const a2 = 0, b2 = 0;
  assign(a2, b2);  // Error: int const is not Assignable
                   // because a = b does not compile (first line
                   // in concept definition block)

  Assignable int const;  // Error: int const can't be declared to be
                         // Assignable, because it doesn't conform
}

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: "Sergey P. Derevyago" <non-existent@iobox.com>
Date: Wed, 4 Apr 2001 14:42:53 GMT
Raw View
Christopher Eltschka wrote:
> Now my suggestion is the following:
>
> 1. Introduce a new keyword "concept" (or maybe __concept),
>    which allows to define a concept "by example": The syntax
>    is basically the same as for a function, except that you
>    have the keyword "concept" in front, and no return type.
>
> Example:
>
> concept Assignable(Assignable& a, Assignable const& b)
> {
>   a = b;
>   Assignable c(b);
> }
 I see some problems with this proposal. There exists some cases where you can
write a = b; but the operator=() modifies its right argument (consider
auto_ptr). So you can't pass Assignable const& b to it. The "concept by
example" should not be defined in such a way.
 A little more generic proposal like

 concept AssignableConcept(a, b, typename C)
 {
   a = b;
   C c(b);
 }

would be much more useful, IMHO.

 And we can use it like this:

void f()
{
 int x;
 long y;

 AssignableConcept(x, y, double);

 // ...
}

Compiler checks the correctness of the following code:

void f()
{
 int x;
 long y;

 {  // AssignableConcept block
  x = y;
  double c(y);
 }

 // ...
}

but doesn't generate any code for the "AssignableConcept block". It behaves
just like a macro but (important!!!) _without_ a code generation.
--
         With all respect, Sergey.          http://cpp3.virtualave.net/
         mailto : ders at skeptik.net

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: Christopher Eltschka <celtschk@dollywood.itp.tuwien.ac.at>
Date: Wed, 4 Apr 2001 14:53:55 CST
Raw View
"Sergey P. Derevyago" wrote:
>
> Christopher Eltschka wrote:
> > Now my suggestion is the following:
> >
> > 1. Introduce a new keyword "concept" (or maybe __concept),
> >    which allows to define a concept "by example": The syntax
> >    is basically the same as for a function, except that you
> >    have the keyword "concept" in front, and no return type.
> >
> > Example:
> >
> > concept Assignable(Assignable& a, Assignable const& b)
> > {
> >   a = b;
> >   Assignable c(b);
> > }
>         I see some problems with this proposal. There exists some cases where you can
> write a = b; but the operator=() modifies its right argument (consider
> auto_ptr). So you can't pass Assignable const& b to it. The "concept by
> example" should not be defined in such a way.

But auto_ptr is *not* assignable, according to the standard.
This is the reason why you may not put it into any standard container.

If you want to allow auto_ptr as well, you can define another concept,
say

concept WeakAssignable(WeakAssignable& a, WeakAssignable& b)
{
  a = b;
  Assignable c(b);
}

So your "problem" is indeed an expressive power. I can specify
if I need assignment from const sources, or if I don't need them.
For example, the standard containers would need Assignable,
while std::swap would be satisfies with WeakAssignable.

>         A little more generic proposal like
>
>  concept AssignableConcept(a, b, typename C)
>  {
>    a = b;
>    C c(b);
>  }
>
> would be much more useful, IMHO.

I don't see what concept this should describe.
"a can be assigned to b, and copied to variables of type C
(which is totally unrelated to a or its type)"
doesn't combine to a well-defined concept in my head.

However, if you *really* want htat "concept", I can define
it as well in my proposal:

template<class B, class C>
 concept YourAssignable(YourAssignable& a, B& b)
{
  a = b;
  C c(b);
}

>
>         And we can use it like this:
>
> void f()
> {
>  int x;
>  long y;
>
>  AssignableConcept(x, y, double);
>
>  // ...
> }

And what exactly would that statement say?
What problem would it solve?

>
> Compiler checks the correctness of the following code:
>
> void f()
> {
>  int x;
>  long y;
>
>  {  // AssignableConcept block
>   x = y;
>   double c(y);
>  }
>
>  // ...
> }
>
> but doesn't generate any code for the "AssignableConcept block". It behaves
> just like a macro but (important!!!) _without_ a code generation.

Well, just like macros are an inferiour solution than templates,
your solution seems inferiour to me than my solution.

BTW, your solutoin can already be done with real macros
(except that macros don't allow to differentiate between
typenames and variable names):

#define AssignableConcept(a, b, C) if(true); else \
{                                                 \
  a = b;                                          \
  C c(b);                                         \
}


In my solution, the relation between types and concepts somewhat
resembles the relation between variables and types. The difference
is that a variable has only one type (but inheritance allows it
to be of several types in some sense), while a type can conform to
several concepts, and that the concept can be attached to a type
afterwards (because it doesn't determine the layout of the class
- that's already specified independently -, but it only checks
that the class layout conforms to the specified interface).

This "late attachment" together with the "conformingness by usage"
concept typical for templates/generic programming should give you
the best of both worlds: On the one hand the flexibility of
templates, on the other hand the safety of strong typing,
applied ty types.

However, one thing in my proposal might be improvable:

Instead of defining the "strictness" of a concept per usage,
it would probably be better to define it directly for the
concept. Because either a concept is well-enough described
by the usage - then you can relax the strictness everywhere -,
or the concept is not sufficiently defined by usage alone
(think of the difference of a semigroup and a group - both
have a multiplication defined, and that's it syntactically),
and then strict conformance should be demanded on every use.

Of course if you look at the keywords you might reuse, the
top two candidates are static and auto ;-)

But fun aside, explicit would be quite good for it:

explicit concept foo(...) { ... }
  explicit concept: types must be explicitly declared to
  conform to it.

concept foo(...) { ... }
  implicit concept: conformance is implicitly checked
  where needed.

Then on usage, there would be only one syntax:

template<foo X> void f(X x) { ... }

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: "Sergey P. Derevyago" <non-existent@iobox.com>
Date: Thu, 5 Apr 2001 23:00:30 GMT
Raw View
Christopher Eltschka wrote:
> > > concept Assignable(Assignable& a, Assignable const& b)
> > > {
> > >   a = b;
> > >   Assignable c(b);
> > > }
> >         I see some problems with this proposal. There exists some cases where you can
> > write a = b; but the operator=() modifies its right argument (consider
> > auto_ptr). So you can't pass Assignable const& b to it. The "concept by
> > example" should not be defined in such a way.
>
> But auto_ptr is *not* assignable, according to the standard.
 Strictly speaking, "auto_ptr does not meet the CopyConstructible and
Assignable requirements for Standard Library container elements and thus
instantiating a Standard Library container with an auto_ptr results in
undefined behavior." There is some subtle difference between assignable and
Assignable :)

> If you want to allow auto_ptr as well, you can define another concept,
> say
>
> concept WeakAssignable(WeakAssignable& a, WeakAssignable& b)
> {
>   a = b;
>   Assignable c(b);
> }
 I don't like function definitions to represent concepts. There are a lot of
issues around their parameters: cv references to cv references, private copy
constructors and so on. I'd like not to specify types for concept parameters.

> > void f()
> > {
> >  int x;
> >  long y;
> >
> >  AssignableConcept(x, y, double);
> >
> >  // ...
> > }
>
> And what exactly would that statement say?
> What problem would it solve?
 It forces compiler to perform an early diagnostics. This is very useful in
generic programming :)

> > Compiler checks the correctness of the following code:
> >
> > void f()
> > {
> >  int x;
> >  long y;
> >
> >  {  // AssignableConcept block
> >   x = y;
> >   double c(y);
> >  }
> >
> >  // ...
> > }
> >
> > but doesn't generate any code for the "AssignableConcept block". It behaves
> > just like a macro but (important!!!) _without_ a code generation.
>
> Well, just like macros are an inferiour solution than templates,
 But not for all problems. Current templates have some "wellknown"
limitations.

> BTW, your solutoin can already be done with real macros
> (except that macros don't allow to differentiate between
> typenames and variable names):
>
> #define AssignableConcept(a, b, C) if(true); else \
> {                                                 \
>   a = b;                                          \
>   C c(b);                                         \
> }
 Yes, but marco has some problems with a scope and, generally speaking, it
doesn't guarantee not to generate any "probe" code. And such probe code will
issue side effects at run-time.
--
         With all respect, Sergey.          http://cpp3.virtualave.net/
         mailto : ders at skeptik.net

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: Christopher Eltschka <celtschk@dollywood.itp.tuwien.ac.at>
Date: Tue, 10 Apr 2001 00:38:10 GMT
Raw View
"Sergey P. Derevyago" wrote:
>
> Christopher Eltschka wrote:
> > > > concept Assignable(Assignable& a, Assignable const& b)
> > > > {
> > > >   a = b;
> > > >   Assignable c(b);
> > > > }
> > >         I see some problems with this proposal. There exists some cases where you can
> > > write a = b; but the operator=() modifies its right argument (consider
> > > auto_ptr). So you can't pass Assignable const& b to it. The "concept by
> > > example" should not be defined in such a way.
> >
> > But auto_ptr is *not* assignable, according to the standard.
>         Strictly speaking, "auto_ptr does not meet the CopyConstructible and
> Assignable requirements for Standard Library container elements and thus
> instantiating a Standard Library container with an auto_ptr results in
> undefined behavior." There is some subtle difference between assignable and
> Assignable :)
>
> > If you want to allow auto_ptr as well, you can define another concept,
> > say
> >
> > concept WeakAssignable(WeakAssignable& a, WeakAssignable& b)
> > {
> >   a = b;
> >   Assignable c(b);
> > }
>         I don't like function definitions to represent concepts. There are a lot of
> issues around their parameters: cv references to cv references, private copy
> constructors and so on. I'd like not to specify types for concept parameters.

But the types are the essence of the concept:
std::vector<char>::iterator is an random access iterator.
That's a property of the type, and only of the type.

As I wrote, concepts, as I see them, are basically "Meta-Types":
Types for types, instead of types for variables. However, since
concepts don't define the structure of the type, they are more
flexible; especially thay can be tagged on afterwards.
The function style definition makes sure that you don't loose
the advantages of genericity.

>
> > > void f()
> > > {
> > >  int x;
> > >  long y;
> > >
> > >  AssignableConcept(x, y, double);
> > >
> > >  // ...
> > > }
> >
> > And what exactly would that statement say?
> > What problem would it solve?
>         It forces compiler to perform an early diagnostics. This is very useful in
> generic programming :)

Well, that's not really the answer to my question.
If I now ask hat this diagnostic tells me, you probably would say
"that the code would not have compiled"?

My point is: What is the _semantics_ which your AssignableConcept
is supposed to describe? I.e.: Why should I want to make a
definition like you showed? And especially: What's better in your
definition than in mine?
If you can't show me that, I'll keep my conclusion that your
proposal is inferior to mine. Simply because mine allows all
semantics I want to express to be expressed (including semantics
which your proposal doesn't allow to be expressed - f.ex.
the Assignable concept excluding auto_ptr-like clases).
Moreover, it does so in a clear way which has similarities to
static typing of variables, and to subclassing, while it
retains the flexibility of generic programming.

>
> > > Compiler checks the correctness of the following code:
> > >
> > > void f()
> > > {
> > >  int x;
> > >  long y;
> > >
> > >  {  // AssignableConcept block
> > >   x = y;
> > >   double c(y);
> > >  }
> > >
> > >  // ...
> > > }
> > >
> > > but doesn't generate any code for the "AssignableConcept block". It behaves
> > > just like a macro but (important!!!) _without_ a code generation.
> >
> > Well, just like macros are an inferiour solution than templates,
>         But not for all problems. Current templates have some "wellknown"
> limitations.

OK, what problem does your proposal solve which mine doesn't?

>
> > BTW, your solutoin can already be done with real macros
> > (except that macros don't allow to differentiate between
> > typenames and variable names):
> >
> > #define AssignableConcept(a, b, C) if(true); else \
> > {                                                 \
> >   a = b;                                          \
> >   C c(b);                                         \
> > }
>         Yes, but marco has some problems with a scope and, generally speaking, it
> doesn't guarantee not to generate any "probe" code. And such probe code will
> issue side effects at run-time.

Generally speaking, the same is true for your proposal. Since
the generation of code is no observable behaviour, the compiler
is free to compile the code. From the view of the compiler
producer, it may even the cheapest implementation to just
internally replace your construct with the macro expansion
I showed - this is an easy implementation strategy which doesn't
need too much change of the compiler.

So your proposal allows to trial-compile code. Mine allows that, too.
But mine gives the concept "concept" a well-defined meaning (which
could be described as meta-type), while I miss such a meaning in
your proposal.
Therefore I conclude that my proposal has more expressive power,
and is therefore preferrable.

Another view of my "concept" proposal is that it reconciles
"is-a" and generic programming, in that it allows (as far as
this is possible at compile-time) to specify exactly what it
means that, f.ex., std::vector<char>::iterator is-a
RandomAccessIterator, _without_ forcing to declare it as such
at class definition time (where you might not even know which
concepts may get invented later, and which are fulfilled by
the class).

BTW, my proposal has a natural extension: Template overloading.
Indeed, Template overloading is already done in the STL for
iterators, by using some complicated machinery combining a
template forwarding function (to do the dispatch), the concept
of iterator_tags (which are classes which actually name the
concept), the traits class mechanism (which is used to associate
the type with it's concept (iterator_category), and inheritance
between iterator_tag types to model the hierarchy of categories.

Now, if this mechanism would be replaced by my "concept"
proposal + template overloading, the implementation would
be quite simple:

template<ForwardIterator Iterator> void advance(...);
template<BidirectionalIterator Iterator> void advance(...);
template<RandomAccessIterator Iterator> void advance(...);

You get inheritance for free by using just the suggested
template syntax I've originally used for things like
istream_iterator<type> together with the fact that you
can use concepts to restrict templates (both are parts
of my original proposal). Example:

// Tell the compiler that each RandomAccessIterator
// is a ForwardIterator
template<RandomAccessIterator Iter> ForwardIterator Iter;

Of course, this also shows that the proposal is not yet
ripe for a Feature Request: First, one would have to
determine an algorithm which can resolve this type
of template (or at least show that such an algorithm
is possible).

Of course, if one doesn't implement template overloading,
one can simply solve the problem by not allowing
conformance declarations to be concept-constrained templates,
therefore the original proposal would work with a few
refinements.

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: Gabriel Dos Reis <dosreis@cmla.ens-cachan.fr>
Date: Fri, 16 Mar 2001 18:47:15 GMT
Raw View
gt5163b@prism.gatech.edu (Brian McNamara!) writes:

[...]

| You should check out the book "Generative Programming", though.
|
|    http://www.generative-programming.org/
|
| They create reusable if/for/while stuff, and show lots of
| meta-programming techniques and goodies.

That book is certainly good -- I regularily recommend it.

However, there ought to be simpler syntax to express basic control
structures.

--
Gabriel Dos Reis, dosreis@cmla.ens-cachan.fr

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: gt5163b@prism.gatech.edu (Brian McNamara!)
Date: Mon, 12 Mar 2001 19:18:05 GMT
Raw View
As people write more and more heavily templated code and template
meta-programs, I've seen a lot of cases where programmers specifically
inject constructs that will force a template instantiation not to
compile.  Sometimes they're simple compile-time assertions, other times
they're more complicated, such as the "concept checks" implemented in
SGI's STL.

The one thing these all have in common is that while they get the job
done (enforce that programs with certain bad template instantiations
fail to compile), the diagnostics they evoke from the compiler tend to
look horrible.  For instance, one compile-time assertion implementation
I've seen mentioned on comp.lang.c++.moderated ensures that
CT_Assert<false> will fail to compile because it tries to create an
array of zero (or negative one) elements.  The error messages from
different compilers vary only in just _how_ugly_ the diagnostics they
produce are.

I would like to propose the following extension.  The code

   Error<true,"some string">();

should render a program uncompilable, whereas

   Error<false,"some string">();

is a harmless no-op.  Furthermore, in the former case, the
implementation should issue a diagnostic along the lines of

   User-defined error: "some string"

Here is a motivating example.  I want certain instantiations of f() not
to compile (see the code for details).  Currently, to get literate
diagnostics, I go through various contortions like this:

   template <class T> struct Error {};

   template <class T, class U, bool b> struct f_error {};
   template <class T, class U> struct f_error<T,U,false> {
      typename Error<T>::T_must_be_smaller_than_U x; // line 5
   };

   template <class T, class U>
   void f() {
      // for some reason, we need to ensure that sizeof(T) < sizeof(U)
      f_error<T, U, sizeof(T) < sizeof(U)>();   // line 11
   }

   int main() {
      f<char,int>();   // ok
      f<int,char>();   // bad                   // line 16
   }

The latest g++ gives a decent error:

   err2.cc: In instantiation of `f_error<int, char, false>':
   err2.cc:11:   instantiated from `void f() [with T = int, U = char]'
   err2.cc:16:   instantiated from here
   err2.cc:5: no type named `T_must_be_smaller_than_U' in `struct Error<int>'

However I only know this will create a decent error message for my
version of g++; another compiler may ignore all my work to get the text
"T_must_be_smaller_than_U" into my error message.  I would like to be
able to just do this:

   template <class T, class U>
   void f() {
      // for some reason, we need to ensure that sizeof(T) < sizeof(U)
      Error<!(sizeof(T) < sizeof(U)), "T must be smaller than U">();
   }

   int main() {
      f<char,int>();   // ok
      f<int,char>();   // bad      // line 9
   }

and have my compiler say

   err2.cc: In instantiation of `void f() [with T = int, U = char]'
   err2.cc:9:   instantiated from here
   err2.cc:4: User-defined error: "T must be smaller than U"

I picked the syntax of Error<condition,"text">(); because it looks like
it could have been a real entity declared as

   template <bool, char *> struct Error {};

In other words, I think it doesn't require modifying the grammar to
implement the extension.  Furthermore, since string literals don't have
external linkage, they're not valid template parameters, so this
extension doesn't supplant any valid code.  Of course, an implementor
would probably want to name it __Error so as not to pollute the user
namespace.

Similarly, __Warning could provide a diagnostic but still allow the
program to compile.

I am interested to know if any implementors have such an extension.  I
think it might be an easy one to add, and it would really be a
_tremendous_ help to those of us doing lots of crazy template stuff.
My goal with this 'pre-proposal' is to generate some discussion, and
hopefully motivate some vendors to supply this extension, so we can
have some "existing practice" to work with for a while in the hopes of
standardizing in the future.

So tell me what you think of the general idea, and let's get crackin'.
:)

--
 Brian M. McNamara   lorgon@acm.org  :  I am a parsing fool!
   ** Reduce - Reuse - Recycle **    :  (Where's my medication? ;) )

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: Dennis Yelle <dennis51@jps.net>
Date: Mon, 12 Mar 2001 21:07:08 GMT
Raw View
"Brian McNamara!" wrote:
[...]
> I would like to propose the following extension.  The code
>
>    Error<true,"some string">();
>
> should render a program uncompilable, whereas
>
>    Error<false,"some string">();
>
> is a harmless no-op.  Furthermore, in the former case, the
> implementation should issue a diagnostic along the lines of
>
>    User-defined error: "some string"

We certainly need something like that,
but I think we actually need more than that.
I think we need a compile time if.
We already have compile time variables, the enum.
Several people have invented tricks with templates to
get the compile time if, but the syntax is ugly, and it
leads to confusing error messages.  I myself have
used ?: to get compile time if, but I think it leads
to confusing code.

I think we need a more complete proposal including at least
compile time versions of if, for, while, error, and warning.

Dennis Yelle
--
I am a computer programmer and I am looking for a job.
There is a link to my resume here:
http://table.jps.net/~vert/

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: gt5163b@prism.gatech.edu (Brian McNamara!)
Date: Mon, 12 Mar 2001 21:38:52 GMT
Raw View
Dennis Yelle <dennis51@jps.net> once said:
>We certainly need something like that,
>but I think we actually need more than that.
>I think we need a compile time if.
>We already have compile time variables, the enum.
>Several people have invented tricks with templates to
>get the compile time if, but the syntax is ugly, and it
>leads to confusing error messages.  I myself have
>used ?: to get compile time if, but I think it leads
>to confusing code.
>
>I think we need a more complete proposal including at least
>compile time versions of if, for, while, error, and warning.

Ah, to dream.  :)

Well yes, everything plus the kitchen sink would be desirable to many of
us.  But I'm aiming much lower, in the hopes of actually having my wish
granted.  I figure the compiler-implementors probably already deal with
the "your template error messages suck" complaints a lot, so they are
already motivated to improve them.  So I'm willing to meet them
halfway--if they'll give me a mechanism to specify error messages, I'll
do the work of doing various detection and creating my own messages for
my template libraries.


You should check out the book "Generative Programming", though.

   http://www.generative-programming.org/

They create reusable if/for/while stuff, and show lots of
meta-programming techniques and goodies.  It may all still be ugly, but
humans are surprisingly adaptive to programming languages, even if they
are ugly.

--
 Brian M. McNamara   lorgon@acm.org  :  I am a parsing fool!
   ** Reduce - Reuse - Recycle **    :  (Where's my medication? ;) )

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: Dennis Yelle <dennis51@jps.net>
Date: Tue, 13 Mar 2001 15:20:21 GMT
Raw View
"Brian McNamara!" wrote:
[...]
> You should check out the book "Generative Programming", though.
>
>    http://www.generative-programming.org/
>
> They create reusable if/for/while stuff, and show lots of
> meta-programming techniques and goodies.  It may all still be ugly, but
> humans are surprisingly adaptive to programming languages, even if they
> are ugly.

Yes, I read that book.
That is why it is so frustrating.
The complexity is already in the compilers,
but to get at it, one has to use incredibly complex and confusing
template tricks.  And they really are tricks, because I am quite sure
that Bjarne did not realize, at the time, that the templates he
invented could be used to build compile time if, for, and while.

I say, do it right, with simple syntax.

Dennis Yelle
--
I am a computer programmer and I am looking for a job.
There is a link to my resume here:
http://table.jps.net/~vert/

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: marco@technoboredom.net (Marco Manfredini)
Date: Tue, 13 Mar 2001 19:01:28 GMT
Raw View
Dennis Yelle <dennis51@jps.net> wrote in <3AAD6F1D.1CCCA3AC@jps.net>:

>"Brian McNamara!" wrote:
>[...]
>> You should check out the book "Generative Programming", though.
>>
>>    http://www.generative-programming.org/
>>
>> They create reusable if/for/while stuff, and show lots of
>> meta-programming techniques and goodies.  It may all still be
>> ugly, but humans are surprisingly adaptive to programming
>> languages, even if they are ugly.
>
>Yes, I read that book.
>That is why it is so frustrating.
>The complexity is already in the compilers,
>but to get at it, one has to use incredibly complex and confusing
>template tricks.  And they really are tricks, because I am quite
>sure that Bjarne did not realize, at the time, that the templates he
>invented could be used to build compile time if, for, and while.
>

I would also believe that. Templates were meant to be templates, but it
turns out that implementing them installs a complete (but fuzzy to use)
meta-language into C++ (well the preprocessor is a kind of meta language
as well), which enables us to write programs that run in the compiler.
I'd suggest one should think a "real" meta language that is easy to
implement and easy to use and look if it could be made powerful enough
to actually implement the current template mechanism in terms of that
meta language.

--
Marco

---
[ 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.research.att.com/~austern/csc/faq.html                ]





Author: "cjoy" <cjoy@houston.rr.com>
Date: Wed, 14 Mar 2001 13:35:30 GMT
Raw View
I think the direction of being able to issue a user defined error message is
the right way to go but I think your initial specification of
Error<condition,"text">();
doesn't really buy you much. To recap the problem let's take a trivial
example e.g. a version of the "find" algorithm in STL:

template <class _InputIter, class _Tp>
inline _InputIter find(_InputIter __first, _InputIter __last,
                       const _Tp& __val)
{
  while (__first != __last && !(*__first == __val))
    ++__first;
  return __first;
}

So to be an input iterator your passed in class would have to pass some
concept checks like:

TrivialIterator requirements:
AssignableConcept;
DefaultConstructibleConcept;
EqualityComparableConcept;
IdentityInveriant, i.e. x == y if and only if &*x == &*y

Forward Iterator Requirements:
PreIncrementOperator;
PostIncrementOperator;
etc.

Ultimately, though, the idea of a generic class as I understand it is really
centered around what kinds of operations can be performed against that
class.  So, to determine if your object lies within the algebra that your
algorithm requires what you would want to do is check whether various kinds
of operations can be performed against your template class e.g.
Error<can_match_function_signature, "User message">
where an error is issued if the given function signature is not matched.
Here the normal C++ lookup rules would have to apply regarding allowed
conversions etc.
So e.g.

template <class _InputIter, class _Tp>
_InputIter find(_InputIter __first, _InputIter __last,
                       const _Tp& __val,
                       input_iterator_tag) {

// CONCEPT CHECKS HERE -- in practice these would live in another template
// Assignable concept
Error<_InputIter(const &_InputIter), "Object not assignable, no copy
constructor");
Error<_InputIter &operator=(const _InputIter &), "Object not assignable, no
assignment operator");
--Invariant property ., x == y  iff &*x == &*y not checked --

// Equality comparable
Error <bool operator == (const _InputIter &lhs, const _InputIter &rhs),
"Object not equality comparible, can't find operator == for this class.")
Error <bool operator != (const _InputIter &lhs, const _InputIter &rhs),
"Object not equality comparible, can't find operator != for this class.")
-- Again invariants not checked
etc.

Naturally, this would still leave holes for more complicated concepts like
the invariant concept for forward iterators, i.e., x == y if and only if &*x
== &*y.
However, I'm doubtful that you can check such invariants at compile time.


Corwin


---
[ 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.research.att.com/~austern/csc/faq.html                ]