Topic: Constraints on parameterized types


Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1999/03/22
Raw View
Biju Thomas wrote:
>
> Nate Lewis wrote:
> >
> > Noel Belcourt wrote in message <7cs3lg$qtg$1@news.sandia.gov>...
> > >Any ideas on how to do this?
> > >template <class T : public X>
> > >class Q {};
> > >
> > >The obviously illegal syntax defines what I'm trying to accomplish.
> > >The idea is to allow any class derived from X to parameterize Q but
> > >prevent classes derived from A.
> >
> > You might want to reconsider this.  One of the benefits of using
> > templates is the ability to write code that will work correctly with
> > types you have not foreseen; to hardcode a derivation requirement is to
> > close off a large path of reuse.
>
> That is right. But, I may not want to enforce some constraints on the
> type of template arguments. Wih the current C++ templates, this
> information is either in the documentation or is distributed all over
> the template code. Both approaches are not very good, IMHO.
>
> > (Note, for example, that the standard
> > container classes don't expect their contents to derive from
> > std::collectable and so forth.)
>
> But, the standard has requirement that the container elements be
> copy-constructible and assignable. This is specified in the document. It
> will be nice to have a syntax for representing this in the code
> explicitly.
>
> I think Eiffel has a syntax for representing this kind of stuff. They
> call it constrained genericity. I don't remember the syntax correctly,
> but, in C++ terms, it can be represented by something like:
>
>   template < class T : ConstraintClass >
>   class Foo { ... };
>
> where, the template argument has to satisfy the interface defined by the
> the class 'ConstraintClass'.
>
> IMHO, if C++ adds some similar feature, it will be useful for
> developers. We won't have many people coming around and complaining that
> std::auto_ptr doesn't work with containers etc. :-)

What about this:

// Helper classes to define constraints:

template<typename T> struct default_constructible
{
  typedef char type[sizeof(0,T())];
};

template<typename T> struct copy_constructible
{
  typedef char type[sizeof(0,T(*(T*)0))];
};

template<typename T> struct assignable
{
  typedef char type[sizeof((*(T*)0)=T(),0)];
                        // lhs must be lvalue for built-in!
};


// Example container class using those constraints:

template<typename T> class MyContainer
{
// constraints:
  typedef typename default_constructible<T>::type
default_constructible_type;
  typedef typename copy_constructible<T>::type copy_constructible_type;
  typedef typename assignable<T>::type assignable_type;
// ...
};


// Some code to see if it works

class NormalClass
{
};

class NotDefaultConstructible
{
public:
  NotDefaultConstructible(int);
};

class NotCopyable
{
public:
  NotCopyable();
private:
  NotCopyable(NotCopyable const&);
};

class NotAssignable
{
private:
  void operator=(NotAssignable const&);
};

int main()
{
  MyContainer<int> C_int;
  MyContainer<NormalClass> C_normal;
  MyContainer<NotDefaultConstructible> C_nodefault;
  MyContainer<NotCopyable> C_nocopy;
  MyContainer<NotAssignable> C_noassign;
}

The "0," in the sizeof constructor expressions are because
egcs-1.0.3 doesn't like it without. I'm not sure, but I think
it should work without in theory. However, it doesn't harm.

OTOH the ",0" in the sizeof assignment expression is necessary
to allow a void return type: sizeof(void) is forbidden, but
through the comma operator, we can avoid that, by giving an
explicit type.

The trick is that you use types to get tests without generating
run-time overhead, and sizeof to get a compile-time expression
(for defining the types) which includes the constraint in
question without actually compiling it (that is, no code is
emitted for the expression).
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: sbnaran@localhost.localdomain (Siemel Naran)
Date: 1999/03/23
Raw View
On 19 Mar 99 07:33:49 GMT, Noel Belcourt <kbelco@sandia.gov> wrote:

>template <class T : public X>
>class Q {};

Nate Lewis has excellent considerations.

However, it is possible to do this.

1.
Use static_cast to force an upcast from Derived* to Base*.
   template <class T>
   class Q {
      public:
         Q() { (void) static_cast<X*>((T*)0); }
   };
Problem 1: notation looks weird, but one gets used to it.
Problem 2: have to put the test in every constructor or destructor.
Problem 3: errors sometimes unveiled only at link time.

One may derive Q<T> from Qassert<T>, and the destructor of Qassert<T>
does all the checks.


2.
Put the test in the class definition.
   template <class T>
   class Q {
      private:
         typedef char assertion[Conversion<T*,X*>::result];
      public:
   };
The template class Conversion<A,B> defines a static const bool
member called 'result' that is #true if there is a conversion
from A to B, and #false otherwise.  Andrei gave the conversion
template in the other newsgroup.  Here's my touching up of it.



// conversion.c
// by Siemel Naran
// idea by Andrei Alexandrescu
// Technique to see if conversions between various types are possible

namespace myspace
{

template <class From, class To>
class conversion
{
     typedef char (&no )[1];
     typedef char (&yes)[2];
     static no  check(...);
     static yes check(To );
     static const From& from();

     public:
         conversion();
         static const bool exists = sizeof(check(from())) == sizeof(yes);
         static const bool exists_2way = exists && conversion<To, From>::exists;
         static const bool same_type = false;
};

} // namespace myspace

/////
/////
/////

#include <typeinfo>
#include <iostream>

template <class From, class To>
void show()
{
     typedef myspace::conversion<From,To> Conversion;
     cout << "From==" << typeid(From).name() << '\n'
          << "To  ==" << typeid(To  ).name() << '\n'
          << "conversion exists? " << Conversion::exists      << '\n'
          << "conversion 2way  ? " << Conversion::exists_2way << '\n'
          << "\n\n";
}

struct B;
struct A { A(); };
struct B : A { B(); };

struct D;
struct C { C(); C(const D&); operator D() const; };
struct D { D(); };

int main()
{
     show<A*,B*>();
     show<B*,A*>();

     show<C,D>();
     show<D,C>();
}

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Joachim Achtzehnter <joachim@kraut.bc.ca>
Date: 1999/03/24
Raw View
> template <class T : public X>
>
> I think Eiffel has a syntax for representing this kind of
> stuff. They call it constrained genericity... IMHO, if C++ adds some
> similar feature, it will be useful for developers.

Stroustrup discusses the pros and cons of this in his book "The Design
and Evolution of C++". Don't remember all the details but one
important reason for not adding such a feature was his contention that
this was a mis-use of inheritence. Constraints on T don't imply that T
must be a sub-type of some other type.

Joachim

--
joachim@kraut.bc.ca      (http://www.kraut.bc.ca)
joachim@mercury.bc.ca    (http://www.mercury.bc.ca)


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Noel Belcourt" <kbelco@sandia.gov>
Date: 1999/03/19
Raw View
Greetings,

Any ideas on how to do this?

class A {};

class B : public A {};

class X {};

class Y : public X {};

template <class T : public X>
class Q {};

The obviously illegal syntax defines what I'm trying to accomplish.
The idea is to allow any class derived from X to parameterize Q but
prevent classes derived from A.

Thanks,

Noel Belcourt
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: "Nate Lewis" <nlewis@mindspring.com>
Date: 1999/03/19
Raw View
Noel Belcourt wrote in message <7cs3lg$qtg$1@news.sandia.gov>...
>Any ideas on how to do this?
>template <class T : public X>
>class Q {};
>
>The obviously illegal syntax defines what I'm trying to accomplish.
>The idea is to allow any class derived from X to parameterize Q but
>prevent classes derived from A.


You might want to reconsider this.  One of the benefits of using
templates is the ability to write code that will work correctly with
types you have not foreseen; to hardcode a derivation requirement is to
close off a large path of reuse.  (Note, for example, that the standard
container classes don't expect their contents to derive from
std::collectable and so forth.)  If you're using this template purely as
a private implementation technique, then I'm not sure a hardcoded
requirement is worthwhile. :)

I don't think there's any way to do this with language syntax, which
would mean you'd need to do it by building some code that would refuse
to compile if your conditions aren't met.  One example might be:

template <class T>
class Q {
public:
  ~Q() { T* t; X* x = t; }
};

Such an implicit pointer conversion between class types will generate a
compile error when Q's destructor is instantiated on a type T that
doesn't derive from X.  (It will also generate a compile error if X is
multiply inherited and is ambiguous, which is bad if it matters to you.)
(There's nothing special about the destructor; it's just the most likely
thing to be instantiated; you could do this with static members to cut
overhead.)

--
Nate Lewis, MCSD
nlewis@mindspring.com




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






Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1999/03/20
Raw View
Noel Belcourt wrote:
>
> Greetings,
>
> Any ideas on how to do this?
>
> class A {};
>
> class B : public A {};
>
> class X {};
>
> class Y : public X {};
>
> template <class T : public X>
> class Q {};
>
> The obviously illegal syntax defines what I'm trying to accomplish.
> The idea is to allow any class derived from X to parameterize Q but
> prevent classes derived from A.

// Untested, but should IMHO work:

template<class T, T* p> struct derived {};

template<class T> class Q
{
  typedef derived<X,(T*)0> T_must_be_derived_from_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://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Biju Thomas <bijuthom@ibm.net>
Date: 1999/03/20
Raw View
Nate Lewis wrote:
>
> Noel Belcourt wrote in message <7cs3lg$qtg$1@news.sandia.gov>...
> >Any ideas on how to do this?
> >template <class T : public X>
> >class Q {};
> >
> >The obviously illegal syntax defines what I'm trying to accomplish.
> >The idea is to allow any class derived from X to parameterize Q but
> >prevent classes derived from A.
>
> You might want to reconsider this.  One of the benefits of using
> templates is the ability to write code that will work correctly with
> types you have not foreseen; to hardcode a derivation requirement is to
> close off a large path of reuse.

That is right. But, I may not want to enforce some constraints on the
type of template arguments. Wih the current C++ templates, this
information is either in the documentation or is distributed all over
the template code. Both approaches are not very good, IMHO.

> (Note, for example, that the standard
> container classes don't expect their contents to derive from
> std::collectable and so forth.)

But, the standard has requirement that the container elements be
copy-constructible and assignable. This is specified in the document. It
will be nice to have a syntax for representing this in the code
explicitly.

I think Eiffel has a syntax for representing this kind of stuff. They
call it constrained genericity. I don't remember the syntax correctly,
but, in C++ terms, it can be represented by something like:

  template < class T : ConstraintClass >
  class Foo { ... };

where, the template argument has to satisfy the interface defined by the
the class 'ConstraintClass'.

IMHO, if C++ adds some similar feature, it will be useful for
developers. We won't have many people coming around and complaining that
std::auto_ptr doesn't work with containers etc. :-)

--
Best regards,
Biju Thomas
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]