TITLE: possible explicit template requirements in code

(Newsgroups: comp.std.c++, 21 Sep 98)


CASPI: zivca@netvision.net.il (Ziv Caspi)

>The most difficult mechanism in C++ to use (for me)
>is writing templates. In many cases, I find myself instantiating
>a template that I wrote, only to discover (via some incomprehensible
>error message) that the template arguments do not have the
>proper form.
>
>For example, if "template<class T> class A {...};" relies on T
>having (say) a member "T::Required()", and I try to instantiate A<>
>with a class that has no such member, I can get some wierd error
>(many times not from A<> directly, but some other templates it uses).
>
>I am considering "compile-time assertions"-like mechanisms in C++
>to help catch these errors as early as possible. The best possibility
>I have so far is to use "using" and "typename" in the front-end
>template so as to get compiler errors if the instantiated arguments
>do not have the proper "form" the template requires. For example,
>
>  template< class T >
>  class A
>    {
>                                // T should have an accessible:
>    using typename T::Required; //   type        called Required
>    using T::Method();          //   method      called Method( void )
>    using T::Data;              //   member data called Data
>    //...
>    };


ABRAHAMS: abrahams@motu.com

I must say, that's an ingenious solution to a couple of problems that are
long-standing. Namely, incomprehensible error reporting, and the lack of a
simple declarative method of showing a template's requirements on its
parameters.


CASPI:

>And the questions are:
>1. Is this guaranteed to work? (There are some elements that cannot
>   be checked in this manner; For example, that T has a private type.
>   However, A cannot then use these elements, so this is not a problem).


ABRAHAMS:

Unfortunately, no. The standard says:

4 A using-declaration used as a member-declaration shall refer to a mem-
  ber  of a base class of the class being defined, shall refer to a mem-
  ber of an anonymous union that is a member of  a  base  class  of  the
  class  being  defined, or shall refer to an enumerator for an enumera-
  tion type that is a member of a base class of the class being defined.
  [Example:
  class C {
          int g();
  };

  class D2 : public B {
          using B::f;             // OK: B is a base of D2
          using B::e;             // OK: e is an enumerator of base B
          using B::x;             // OK: x is a union member of base B
          using C::g;             // error: C isn't a base of D2
  };


CASPI:

>2. Can you think of a better method?


ABRAHAMS:

Well, you could amend your method as follows:

  template< class T >
  class A
  {
    struct TRequirements : T
    {
                                    // T should have an accessible:
        using typename T::Required; //   type        called Required
        using T::Method();          //   method      called Method( void )
        using T::Data;              //   member data called Data
    }
    //...
  };


CASPI:

>3. Are there any obstacles I overlooked?


ABRAHAMS:

Yes, well, of course this method requires that T be a struct or class which
can be subclassed (yes, it is possible to prevent subclassing). So built-in
types won't work as type parameters. Also, the standard goes on to say:

[Note: since constructors and destructors do not have
  names, a using-declaration cannot refer to a constructor or a destruc-
  tor  for  a base class...]

And also, you might want to require that a type be able to be compared with
operator==(), for example. But operator==() can be either a member or a free
function, and if it is a free function, there's no appropriate
using-declaration you can write. And I'm not even sure whether the nested
class TRequirements is required to be instantiated if it's not explicitly
used, which might defeat this whole approach... But don't give up yet; maybe
you can find a way around these problems. It looks promisi

-----

(Newsgroups: comp.std.c++, 21 Sep 98)


CASPI: zivca@netvision.net.il (Ziv Caspi) wrote:

>> I am considering "compile-time assertions"-like mechanisms in C++
>> to help catch these errors as early as possible.
>>
>> template< class T >
>> class A
>> {
>> // T should have an accessible:
>> using typename T::Required; //   type        called Required

OLIVA: Alexandre Oliva <oliva@dcc.unicamp.br>

`using' is just an alias to a name, be it a typename or not.  IMO,
this declaration should be rejected.

CASPI:

>> using T::Method();          //   method      called Method( void )

OLIVA:

And so should this one.  Remember, using has to do with names; what
the name means is not important.

ABRAHAMS: abrahams  <abrahams@motu.com>

> I must say, that's an ingenious solution to a couple of problems that are
> long-standing. Namely, incomprehensible error reporting, and the lack of a
> simple declarative method of showing a template's requirements on its
> parameters.
>
>   template< class T >
>   class A
>   {
>     struct TRequirements : T

OLIVA:

This won't necessarily fail, unless A<T>::TRequirements is explicitly
referenced elsewhere.  Here's an alternate solution I've just
designed:

namespace Requirements {
  template <typename T> struct A {
#ifdef EGCS_BUG
    struct test {
      test() 
#else
    typedef A test;
    A()
#endif
	{ if (0) for_T(); }
    private:
      void for_T() { // never executed
	typedef typename T::type type; // T::type must be accessible
	void (T::*m)() = &T::method; // T::method must have this signature
	int T::*d = &T::data;        // T::data must have type int
	void (*sm)() = &T::stmethod; // static method
	int *sd = &T::stdata;        // static data member
	T* t = new T(/*args*/);      // constructors
	delete t;                    // destructor
      }
#ifdef EGCS_BUG
    };
#endif
    static test check;
  };
  template <typename T> A<T>::test A<T>::check;
}
template <typename T,
          Requirements::A<T>::test*/*unnamed*/ = &Requirements::A<T>::check >
class A {};
class t {
#ifdef PUBLIC
public:
#endif
  ~t() {};
#ifdef MEMBERS
  typedef int type;
  type data;
  static type stdata;
  void method() {};
  static void stmethod() {};
#else
  t(int);
#endif
};
#ifdef MEMBERS
t::type t::stdata;
#endif
A<t> test;

Compiled with egcs 1.1 -DEGCS_BUG (without -DMEMBERS), this code
snippet produces:

test.cc: In method `void A<t>::test::for_T<t>()':
test.cc:10:   instantiated from `A<t>::test::test<t>()'
test.cc:26:   instantiated from here
test.cc:13: no type named `type' in `class t'
test.cc:13: warning: ANSI C++ forbids typedef which does not specify a type
test.cc:14: `method' is not a member of type `t'
test.cc:15: `data' is not a member of type `t'
test.cc:16: `stmethod' is not a member of type `t'
test.cc:17: `stdata' is not a member of type `t'
test.cc:18: no matching function for call to `t::t ()'
test.cc:45: candidates are: t::t(const t &)
test.cc:43:                 t::t(int)
test.cc:35: `t::~t()' is private
test.cc:19: within this context

With -DEGCS_BUGS -DPUBLIC -DMEMBERS, it only complains that main() is
undefined.

Of course, egcs should have written Requirements::A<t>:: in the error
messages.  Furthermore, the helper class Requirements::A<t>::test is
only needed because otherwise egcs would complain that
Requirements::A<t> is not a complete type.

Unfortunately, this requires the declaration of the template class A
to be modified, but the error messages are quite clear.  Note that,
with this separation between the template class and its requirements,
specializing a template class does not necessarily specializes its
requirements, but it is possible to specialize them too.

The main drawback of this approach is that, if the template is given
privileged (friend) access to a class, so must the Requirements
tester.

ABRAHAMS:

> And also, you might want to require that a type be able to be compared with
> operator==(), for example.

OLIVA:

`*(T*)0 == *(T*)0' within for_T() should do it.  Note that it does not 
invoke undefined behavior, because for_T() is never executed.


-----

(Private correspondence)

VANDEVOORDE: David Vandevoorde <daveed@cup.hp.com>

> OLIVA: Alexandre Oliva <oliva@dcc.unicamp.br>
> 
> `using' is just an alias to a name, be it a typename or not.  IMO,
> this declaration should be rejected.
 
The 'typename' keyword is in fact acceptable here
([namespace.udecl] 7.3.3/1), though I agree with
Alexandre that it is somewhat counter-intuitive.
 
The implication of typename in this context is a
bit vague in the C++ standard, but if I put all
pieces together, I think it means that if it turns
out a non-type name is brought into scope by this
using-declaration, a diagnostic should be issued.


