Topic: Proposal: templated typedefs


Author: m@remove.this.part.rtij.nl (Martijn Lievaart)
Date: Sat, 17 Jan 2004 22:58:49 +0000 (UTC)
Raw View
Working on some heavily templated code, I more and more felt the need for
templated typedefs. The situations were many, but let me give two
examples, one short and sweet, the other starts simple and gets more
complex in the end, showing how this proposal can reduce the complexity.

I'm working on a project where everything is templated on a character
type, much like the C++ basic_string, basic_ios, etc. However, everything
is also templated on many more types.

For a simple introduction to the problem consider this. I would like to be
able to define a new templated type `stringvector<T>', which in effect is
a std::vector< std::basic_string<T> >. Currently, there is no generic way
to do so, so I'm forced to to exactly the same typedef in every templated
class.

How much easier would life be if I could just say:

template<typename CHAR>
typedef std::vector< std::basic_string<CHAR> > stringvector;

If I would be allowed to do that, I could just use a stringvector<char>,
stringvector<wchar>, etc, everywhere. I could not find any solution to
this problem apart from repeating the typedef inside every templated
class. Of course, I could create a templated class stringvector and expose
the interface I want, but I want this to be a vector! Inheritance is
obviously also out of the question.

I know this is just syntactic sugar, but allowing this lets us more
clearly express our intentions. I want to use the type std::vector<
std::basic_string<T> >, but I don't want to be forced to spell it out
every time I use it.

But it gets worse. I'll show a situation where it is still syntactic
sugar, but the problem gets forced on the user of my library.

Let's first examine a policy and single type driven pimpl design. I'll
expand on this later to show the problem more clearly.

 // different policies we can pass to an implementation
 template<typename CHAR> struct policy1 {};
 template<typename CHAR> struct policy2 {};

 // the basic implementation, defines typeless (wrt T1, T2) interface
 template<typename CHAR, template<class X> P>
  class impl_base
  {
  };

 template<typename T2, typename CHAR, template<class X> P>
  class impl : public impl_base<CHAR, P>
  {
  };

 template<typename CHAR=char, template<class X> P=policy1>
  class basic_A
  {
  public:
   template<typename T2>
   basic_A(T2) : pimpl_(new impl<T2, CHAR, P>()) {}
  private:
   impl_base<CHAR, P> *pimpl_;
  };

Now I want to define a type A, which is basically a basic_A with CHAR
being a char, and a type wA for basic_A using a wchar.

Also, I want to define a type basic_B, where the policy P is fixed on
policy2, and from there create types B and wB analogous to A and wA.

This can be mostly done, bending the requirements slightly:

 typedef basic_A<char> A;
 typedef basic_A<wchar> wA;
 typedef basic_A<char, policy2> B;
 typedef basic_A<wchar, policy2> wB;

Where we bend the requirements is to say that a user who wants to specify
a different policy himself, must use basic_A, he cannot use A or wA. No
big deal. But we cannot define our basic_B as easily, so a user who wants
a B, but for an int instead of a char, is forced to know about basic_A and
policy2. I want this knowledge tied to some (templated) type.

This could be solved easily with templated typedefs:

 template<template<class X> P=policy1> typedef basic_A<char, P> A;
 template<template<class X> P=policy1> typedef basic_A<wchar, P> wA;

 template<typename CHAR=char> typedef basic_A<CHAR, policy2> basic_B;
 typedef basic_B<char> B;
 typedef basic_B<wchar> wB;

Now on to a more realistic situation, one I faced today. I still
simplified it for this post, but it shows my problem in full. It is the
same problem as before, but now we template basic_A on one more type.

 // different policies we can pass to an implementation
 template<typename CHAR> struct policy1 {};
 template<typename CHAR> struct policy2 {};

 // the basic implementation, defines typeless (wrt T1, T2) interface
 template<typename CHAR, template<class X> P>
  class impl_base
  {
  };

 template<typename T1, typename T2, typename CHAR, template<class X> P>
  class impl : public impl_base<CHAR, P>
  {
  };

 template<typename T1, typename CHAR=char, template<class X> P=policy1>
  class basic_A
  {
  public:
   template<typename T2>
    basic_A(T2) : pimpl_(new impl<T1, T2, CHAR, P>()) {}
  private:
   impl_base<CHAR, P> *pimpl_;
 };

Here we cannot easily define even our A, wA, B or wB anymore. The only
sane way I see is to inherit from basic_A. (In reality, I split off a
common baseclass and fixed P on policy1 for basic_A). But this has a
number of disadvantages:

- We must duplicate every constructor. In my real code there are several
heavy templated constructors and besides A and B there are C, D and E, so
this is a maintenance nightmare.

- All types become distinct. This makes it unnecessary hard to write code
that uses basic_A and it's cousins. Have a look at 14.8.4.2 para 5 for an
example of what this introduces.

This would be solved easily with a templated typedef, I guess you can
figure it out for yourself by now:

 template<typename T, template<class X> P=policy1>
  typedef basic_A<T, char, P> A;
 template<typename T, template<class X> P=policy1>
  typedef basic_A<T, wchar, P> wA;

 template<typename T, typename CHAR=char>
  typedef basic_A<T, CHAR, policy2> basic_B;

 template<typename T> typedef basic_B<T, char> B;
 template<typename T> typedef basic_B<T, wchar> wB;

The great advantage of this is that I can typedef some types that
encapsulate common situations, showing the user of my library a much
simplified view he can use much more easily. One can compare this to how
string and wstring relate to basic_string, but with one level more of
templating.

Another way to look upon it, is as we can specify more than one default
for a template parameter. In the above code, basic_A defaulted to policy1.
This made sense in my situation, policy2 was a clear exception. However,
things are not always so clear cut:

 class policy1 {};
 class policy2 {};

 // which policy should be the default?
 template<typename T, typename P=???> class X {};

 // OK, do it the hard way
 template<typename T>
  class X1 : public X<T, policy1> {};
 template<typename T>
  class X2 : public X<T, policy2> {};

If class X is very complex, subclassing is unattractive. Also, there are
situations where we don't want to introduce new types. Would be great if
we can just say:

 template<typename T, typename P> class X {};
 template<typename T> typedef X<T, policy1> X1;
 template<typename T> typedef X<T, policy2> X2;

I don't see how this proposal can break any existing code. I also see no
drawbacks to allowing this, only advantages. In fact, I think this can
help keep your sanity while (meta) template-programming.

So my questions:

1) Am I sane? I cannot be the first one to have thought about this, it
just makes so much sense. Searching Google did not turn up anything, but
maybe I just haven't sought hard enough. Usually if I have a good idea,
several others already thought of it, so this makes me think I probably
missed something obvious, or it is a bad idea for some reason[1].

2) Do others recognise the problem I describe?

3) Does anyone see drawbacks?

4) Would this be a useful for C++0x? Do I have to do something else to
propose it for C++0x (if it is deemed useful here)?

Regards,
M4

[1] If I have a bad idea, most often several others had the same bad
idea as well. So this is not really a good argument. :-)

---
[ 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: galathaea@excite.com ("galathaea")
Date: Sun, 18 Jan 2004 03:10:27 +0000 (UTC)
Raw View
"Martijn Lievaart" wrote:
: Working on some heavily templated code, I more and more felt the need for
: templated typedefs. The situations were many, but let me give two
: examples, one short and sweet, the other starts simple and gets more
: complex in the end, showing how this proposal can reduce the complexity.

[...]

: How much easier would life be if I could just say:
:
: template<typename CHAR>
: typedef std::vector< std::basic_string<CHAR> > stringvector;

It is a very good idea.  In fact, it is already being considered!  You
appear to have thought deeply about the problem, so I might suggest taking a
look at http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1489.pdf
which is a nice recent paper by Bjarne Stroustrup and Gabriel Dos Rios about
just this issue.  Early talk focused on templated typedefs, but now-a-days,
it looks more feasible to modify the use of the keyword "using" which may
have more conceptual clarity for aliasing.

--
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

galathaea: prankster, fablist, magician, liar


---
[ 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: usenet@nmhq.net (Niklas Matthies)
Date: Sun, 18 Jan 2004 18:09:40 +0000 (UTC)
Raw View
On 2004-01-17 22:58, Martijn Lievaart wrote:
:
> template<typename CHAR>
> typedef std::vector< std::basic_string<CHAR> > stringvector;
>
> If I would be allowed to do that, I could just use a
> stringvector<char>, stringvector<wchar>, etc, everywhere.

Here's a workaround you might consider:
Define

   template <typename CHAR>
   struct stringvector
   {
      typedef std::vector< std::basic_string<CHAR> > type;
   };

and then use stringvector<char>::type and stringvector<wchar>::type.
A similar solution might also help with your other example (I didn't
follow it in detail).

-- Niklas Matthies

---
[ 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: technews@kangaroologic.com ("Jonathan Turkanis")
Date: Sun, 18 Jan 2004 18:14:42 +0000 (UTC)
Raw View
"Martijn Lievaart" <m@remove.this.part.rtij.nl> wrote in message
news:pan.2004.01.17.15.20.52.828073@remove.this.part.rtij.nl...
> Working on some heavily templated code, I more and more felt the
need for
> templated typedefs. The situations were many, but let me give two
> examples, one short and sweet, the other starts simple and gets more
> complex in the end, showing how this proposal can reduce the
complexity.
>

>
> 1) Am I sane? <snip>

It's hard to say. Could you give some more data? ;-)

> 2) Do others recognise the problem I describe?

The need for typdef templates is widely recognized. See

   http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1489.pdf

Jonathan


---
[ 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                       ]