Topic: current_class: recap (was: current_class: Submitted Public Comment)
Author: vandevod@avs.cs.rpi.edu (David Vandevoorde)
Date: 1995/07/14 Raw View
Let me recap some of what has been said so far and comment on it.
Correct me if I'm wrong.
---
The "Actors" are:
1) "Herb"
Herb Sutter 2228 Urwin, Ste 102 voice (416) 618-0184
Connected Object Solutions Oakville ON Canada L6L 2T2 fax (905) 847-6019
2) "Esa"
Esa Pulkkinen | C++ programmers do it virtually
E-Mail: esap@cs.tut.fi | everywhere with a class, resulting
WWW : http://www.cs.tut.fi/~esap/ | in multiple inheritance.
3) "Joe"
-- Joe Buck <jbuck@synopsys.com> (not speaking for Synopsys, Inc)
Anagrams for "information superhighway": Enormous hairy pig with fan
A rough whimper of insanity
4) "Daveed"
(Myself: vandevod@cs.rpi.edu alias David Vandevoorde)
---
Herb was the first to propose a notation "current_class" that indicates
within a base object the class of the complete object to which that
base object belongs. Specifically, he wrote:
> Allowing base classes to know the exact type of the current most-
> derived class in which they are being used is a great advantage at
> low cost, because it is both:
>
> a) essential for writing entire classes of mixin designs,
> particularly generic patterns; and
>
> b) implementable with one keyword without impacting the existing
> language rules.
Using Herb's original notation, we could have:
struct B { current_class* p; };
struct D: B {};
struct DD: D {};
void f() {
B b; // type of b->p is B*
D d; // type of d->p is D*
DD dd; // type of dd->p is DD*
}
Herb also recognized that this could be done through rewrite-rules
similar to those implied by template instantiation. However, I mentioned
likely difficulties with the "implicit template character" of the
proposed feature (i.e., the need for rewrite rules may not be apparent
from the declaration alone) and Esa showed us the following notation
(adapted for the example above):
template<typename T = current_class>
struct B {
T* p;
};
with the added danger/flexibility (depending on the point-of-view)
that the "current_class" default argument can be overridden by
client code (e.g., through inheritance). I tried to somewhat
formalize the added feature:
> A type-parameter for a class template or a member function
> template can have a default value ["current_class"], indicating
> that the type-parameter should be subsituted by the complete
> class for which the template is instantiated.
...but in a later example:
> template<typename T = current_class>
> struct singleton {
> static T* instance() const
> { return instance_? instance_: (instance_ = new T); }
> protected:
> singleton() {}
> private:
> static T* instance_;
> };
>
> struct B1: singleton {
> };
>
> template<typename T = current_class>
> struct B2: singleton<T> {
> };
>
> struct D1: B1 {
> };
>
> struct D2: B2 {
> };
>
> f() {
> D2* d2 = D2::instance(); // OK
> D1* d1 = D1::instance(); // Not OK, requires a cast.
> }
I put a lot of work back in the hands of the client code. Herb
argued convincingly that this scheme buys us little since by just
manually replacing the "current_class" specifier by the actual
class name, we'd get an equivalent result without relying on
any extension but without the benefits of his original proposal.
Thus it seems we are heading towards a slightly more complex scheme
where the "current_class dependency" is implicitly inherited. In the
former example, we might instead write:
struct B2: singleton {}
struct D2: B2 {}
// B1, D1 unachievable.
Note that this introduces a new difficulty: until now, the template
character of a declaration was always identified by a prefix "template"
token. Now, a parser must wait until the base classes have been
encountered until it can be sure that it is processing a real class
and not a template. This is probably doable, but may significantly
increase the complexity of certain simple development tools.
I'm interested in a workarounds for that situation...
Finally, Joe pointed out that current_class could be used with virtual
functions (unlike regular member function templates) and nicely achieve
covariant return types:
>class Clonable
>{
>public:
> template <class T=current_class> virtual T * clone() const
> {
> return new T(*this);
> }
>};
>
>Now each class derived from Clonable has a proper clone method.
>
>Here we see why this is very different from a template: you can safely
>make a virtual function this way because there's exactly one function
>in each class, where existing member templates correspond to an indefinite
>number of functions.
I summarized three distinct usages for "current_class":
> a) As a more or less regular default template argument (which could be
> overridden in client code). [Esa's] original notation:
>
> template<typename T = current_class> ...
>
> seems quite appropriate here.
>
> b) I assume it would be nice to prevent overriding in some situations.
> I'm not sure what a good notation for this would be. Here are some
> ideas:
> template<typename = current_class> ...
> template<current_class> ...
> class C<current_class> { current_class* f<current_class>(...
> (The last option is inspired by the template specialization syntax).
> I suspect this is orthogonal to the "current_class" issue.
>
> c) To indicate automatic covariance in virtual member functions.
> IMHO, the "template argument" should not be overridable in this
> case. The notation from b) could be applied to virtual member
> functions, or [Esa's] proposed use of "virtual" may be good.
> Maybe:
> struct S {
> template<virtual> X f(...) // virtual member function with
> // covariant "current_class"
> // template-like argument.
> // ...
> };
Herb favors not to allow option 'a' and enforce 'b'/'c' with the
notation template<current_class>.
I'm mostly worried about grammatical issues and implementation in
general.
Looking forward to suggestions...
Daveed