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 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 `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 > 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::TRequirements is explicitly referenced elsewhere. Here's an alternate solution I've just designed: namespace Requirements { template 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 A::test A::check; } template ::test*/*unnamed*/ = &Requirements::A::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 test; Compiled with egcs 1.1 -DEGCS_BUG (without -DMEMBERS), this code snippet produces: test.cc: In method `void A::test::for_T()': test.cc:10: instantiated from `A::test::test()' 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:: in the error messages. Furthermore, the helper class Requirements::A::test is only needed because otherwise egcs would complain that Requirements::A 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 > OLIVA: Alexandre Oliva > > `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.