Topic: Better forward declaration support for typedef.


Author: philippe_mori@hotmail.com ("Philippe Mori")
Date: Tue, 28 Jan 2003 19:35:30 +0000 (UTC)
Raw View
>
> We need to nail down exactly what would compile with forward declarations,
> and what the semantics are if it does. Eg:
>
>     struct Base {
>         virtual void proc( int *p );
>     };
>
>     typename sometype;
>
>     struct Derived : Base {
>         virtual void proc( typename *p );
>     };

I suppose you mean sometype instead of typename in parameter list...

>
> Does this compile? If so, does Derived::proc() override Base::proc()? How
> does the compiler figure it out? What does it need to know, and when?
>

Ok I see the problem... for overriden functions. The simplest solution would
be to consider it illegal to override a function with a different typename
if
that typename is not known. So the solution is a bit less usefull but not
that
much since:

    a) The function may be non-virtual (and without overload) which
limits the potential problems a bit...

    b) If the derived class is in another file, then the file with the base
does not need the extra definition and if the client typically uses the
base class (for polymorphism) most of the files will compile with
limited depedencies.

    c) It is possible to uses the same type in both places if this is what
we intent and will not cause any compilation error (and it is what we
want most of the time).

    d) We typically never want a a derived (virtual) function with
differents arguments list so it will not be so bad if the inclusion
of the definition is required in those special cases.

Forward declaration is a good tool to reduce compilation
depedencies. It does not replace a good design but it can help
a lot to reduce depedencies in existing code that include far
too much file for any reasons... And limited depedencies help
a lot to reduce compilation time particulary when doing partial
rebuild.



---
[ 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: brangdon@cix.co.uk (Dave Harris)
Date: Mon, 27 Jan 2003 20:27:22 +0000 (UTC)
Raw View
philippe_mori@hotmail.com ("Philippe Mori") wrote (abridged):
> --- file.h ---
>
>     class A;
>     class B;
>     class A2;    // A2 derives publicly from A
>
>     void f(A *);
>     void f(B *);
>
>     void f2(A2 *);
>
> --- file1.cpp ---
>
>     void g1(A2 *p) {
>         f(p);    // Which f to call here?
>     }

That will fail to compile, right?

We need to nail down exactly what would compile with forward declarations,
and what the semantics are if it does. Eg:

    struct Base {
        virtual void proc( int *p );
    };

    typename sometype;

    struct Derived : Base {
        virtual void proc( typename *p );
    };

Does this compile? If so, does Derived::proc() override Base::proc()? How
does the compiler figure it out? What does it need to know, and when?

If it doesn't compile, then I really have to wonder what these forward
declarations are useful for. All I did was declare a function - I didn't
try to call it or define it.

I like the general idea of forward declarations, and I certainly see the
motivation. Convince me that there is a set of rules which is both simple
and useful.

  Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
      brangdon@cix.co.uk      |   And close your eyes with holy dread,
                              |  For he on honey dew hath fed
 http://www.bhresearch.co.uk/ |   And drunk the milk of Paradise."

---
[ 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: allan_w@my-dejanews.com (Allan W)
Date: Fri, 24 Jan 2003 22:13:49 +0000 (UTC)
Raw View
brangdon@cix.co.uk (Dave Harris) wrote
> allan_w@my-dejanews.com (Allan W) wrote (abridged):
> > >     typename MyType;
> > >
> > >     int proc( int x ) { return 1; }
> > >     int proc( MyType x ) { return 2; }
> > >
> > >     // typedef int MyType; ???
> >
> > This could be undefined behavior.
>
> How and when could it be diagnosed?

Sorry -- I meant to say that no diagnostic would be required.

> >         sometype   *pt; // No problem
>
> Supposing that sizeof(char *) != sizeof(int *), what is sizeof(pt)?

Frankly, I had forgotten that this was possible. It never has been,
on any platform I used... however, I did used to use a C compiler
where pointer-to-function was a different size than pointer-to-data.

I believe that pointers to data are always the same size, except for
cv-char*, pointer-to-function, and pointer-to-member -- is that right?
If so, maybe we could say that a forward-declaration of a pointer to
data must eventually refer to some pointer to anything EXCEPT cv-char*
and pointer-to-member? Haven't really thought this out.

> And how can the compiler translate calls?
>
>      // First translation unit.
>      struct somestruct; // Incomplete struct
>      typename sometype; // Incomplete something
>
>      void proc1( somestruct *x );
>      void proc2( sometype *y );
>
>      void foo( somestruct *x, sometype *y ) {
>          proc1( x );
>          proc2( y );  // What does this call?
>      }
>
>      // Second translation unit
>      struct somestruct;
>      typedef int sometype;
>
>      void proc1( somestruct *x ) {
>          cout << "We can get here\n";
>      }
>
>      void proc2( int *y ) {
>          cout << "How do we get here\n";
>      }
>
>      void proc2( double *y ) {
>          cout << "... and not here\n";
>      }

I'd say that the linker needs more information than it did in the past.
The code above would have to "know" that a call to proc2(sometype*)
matches a call to proc2(int*).

An even bigger problem is if they don't match.

    // Tran Unit 1
    typename sometype; // Incomplete something
    void proc1(sometype *y);
    void proc2(sometype *y);
    void foo(sometype*y) { proc1(y); proc2(y); }

    // Tran Unit 2
    typedef int sometype;
    proc1(int*y) { ... }

    // Tran Unit 3
    typedef double sometype;
    proc1(double*y) { ... }

Today, the ODR rule doesn't include typedefs. I'd hate to give up
that flexibility just for the ability to enhance forward
declarations...

> But since we can write ... wrapper classes already, it doesn't
> seem worth a language change.

I see your point.

---
[ 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: philippe_mori@hotmail.com ("Philippe Mori")
Date: Sat, 25 Jan 2003 00:28:18 +0000 (UTC)
Raw View
> >
> >      struct somestruct; // Incomplete struct
> >      typename sometype; // Incomplete something
> >
> >      void proc1( somestruct *x );
> >      void proc2( sometype *y );
> >
> >      void foo( somestruct *x, sometype *y ) {
> >          proc1( x );
> >          proc2( y );  // What does this call?
> >      }
> >
> [...]
>
> Today, the ODR rule doesn't include typedefs. I'd hate to give up
> that flexibility just for the ability to enhance forward
> declarations...
>

Enhancing forward declarations would allows to limit compilation
depedencies a lot in real life program. In fact, when typedef are
currently used we cannot easily do forward declarations (and if we
do it will be a maintenance problem if the typedef ever change).

I will give an example with classes forward declaration that will
show even today, we do need the definition in some situation.
Enhanced forward declaration will require definition in some case
but currently we need anyway all the time (i.e. in all translation units
and not only when used).

--- file.h ---

    class A;
    class B;
    class A2;    // A2 derives publicly from A

    void f(A *);
    void f(B *);

    void f2(A2 *);

--- file1.cpp ---

    void g1(A2 *p) {
        f(p);    // Which f to call here?
    }

--- file2.cpp ---

    void g2(A2 *p) {
        f2(p);    // Exact match --- no need for definition
    }


Even today we cannot always tell without full definition what to do.
In a case like the one above, we need the definition of A2 since we
not have an exact match. It would be essentially the same thing with
extended forward declaration.

If function g1 is is a source file (file1.cpp in example above) , only
that translation unit needs to know A2. Another translation unit like
file2.cpp would not require the definition of A2 since there is an exact
match.

So no much different that what happens today with classes forward
declaration except that it would also works for typedef.



---
[ 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: allan_w@my-dejanews.com (Allan W)
Date: Wed, 22 Jan 2003 18:27:09 +0000 (UTC)
Raw View
brangdon@cix.co.uk (Dave Harris) wrote in message news:<memo.20030118170839.23385D@brangdon.madasafish.com>...
> philippe_mori@hotmail.com ("Philippe Mori") wrote (abridged):
> > I would like to be able to do forward declaration without having to
> > know if a type is a struct, a class, an enum, a typedef...
>
> This is a fairly common request. One problem is that the real type is
> needed for overloading. We're obviously not allowed to write:
>
>     int proc( int x ) { return 1; }
>     int proc( int x ) { return 2; }
>
> and so what is the compiler to make of:
>
>     typename MyType;
>
>     int proc( int x ) { return 1; }
>     int proc( MyType x ) { return 2; }
>
>     // typedef int MyType; ???

This could be undefined behavior.

> > I think it could even work if typename is also used for declaring
> > variable of unknown type:
>
> What if the type turns out to have a private constructor?

Same as any other incomplete type -- you can't construct one.

    struct somestruct; // Incomplete struct
    typename sometype; // Incomplete something

    void foo() {
        somestruct s; // Illegal for incomplete types
        sometype   t; // Illegal for incomplete types
        somestruct *ps; // No problem
        sometype   *pt; // No problem
    }

    // Define them
    struct somestruct { int i; };
    typedef std::vector<int> sometype;

    void bar() {
        somestruct s; // Now legal
        sometype   t; // Now legal
        somestruct *ps; // No problem
        sometype   *pt; // No problem
    }

---
[ 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: brangdon@cix.co.uk (Dave Harris)
Date: Wed, 22 Jan 2003 21:22:01 +0000 (UTC)
Raw View
allan_w@my-dejanews.com (Allan W) wrote (abridged):
> >     typename MyType;
> >
> >     int proc( int x ) { return 1; }
> >     int proc( MyType x ) { return 2; }
> >
> >     // typedef int MyType; ???
>
> This could be undefined behavior.

How and when could it be diagnosed?


>         sometype   *pt; // No problem

Supposing that sizeof(char *) != sizeof(int *), what is sizeof(pt)?

And how can the compiler translate calls?

     // First translation unit.
     struct somestruct; // Incomplete struct
     typename sometype; // Incomplete something

     void proc1( somestruct *x );
     void proc2( sometype *y );

     void foo( somestruct *x, sometype *y ) {
         proc1( x );
         proc2( y );  // What does this call?
     }

     // Second translation unit
     struct somestruct;
     typedef int sometype;

     void proc1( somestruct *x ) {
         cout << "We can get here\n";
     }

     void proc2( int *y ) {
         cout << "How do we get here\n";
     }

     void proc2( double *y ) {
         cout << "... and not here\n";
     }

I suspect for this to work the "typename" would have to create a new type,
so that the above code would become equivalent to:

     // First translation unit.
     struct somestruct; // Incomplete struct
     struct sometype; // Incomplete something

     void proc1( somestruct *x );
     void proc2( sometype *y );

     void foo( somestruct *x, sometype *y ) {
         proc1( x );
         proc2( y );
     }

     // Second translation unit
     struct somestruct;
     struct sometype { int value; }

     void proc1( somestruct *x ) {
         cout << "We can get here\n";
     }

     void proc2( sometype *y ) {
         proc2( &y->value );
     }

     void proc2( int *y ) {
         cout << "How do we get here\n";
     }

     void proc2( double *y ) {
         cout << "... and not here\n";
     }

But since we can write such wrapper classes already, it doesn't seem worth
a language change.

  Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
      brangdon@cix.co.uk      |   And close your eyes with holy dread,
                              |  For he on honey dew hath fed
 http://www.bhresearch.co.uk/ |   And drunk the milk of Paradise."

---
[ 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: brangdon@cix.co.uk (Dave Harris)
Date: Mon, 20 Jan 2003 18:31:53 +0000 (UTC)
Raw View
philippe_mori@hotmail.com ("Philippe Mori") wrote (abridged):
> I would like to be able to do forward declaration without having to
> know if a type is a struct, a class, an enum, a typedef...

This is a fairly common request. One problem is that the real type is
needed for overloading. We're obviously not allowed to write:

    int proc( int x ) { return 1; }
    int proc( int x ) { return 2; }

and so what is the compiler to make of:

    typename MyType;

    int proc( int x ) { return 1; }
    int proc( MyType x ) { return 2; }

    // typedef int MyType; ???


> I think it could even work if typename is also used for declaring
> variable of unknown type:

What if the type turns out to have a private constructor?

  Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
      brangdon@cix.co.uk      |   And close your eyes with holy dread,
                              |  For he on honey dew hath fed
 http://www.bhresearch.co.uk/ |   And drunk the milk of Paradise."

---
[ 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: philippe_mori@hotmail.com ("Philippe Mori")
Date: Fri, 17 Jan 2003 16:26:10 +0000 (UTC)
Raw View
I would like to be able to do forward declaration without having to know if
a type is a struct, a class, an enum, a typedef...

    typename std::string;    // Do not need to know that string is a typedef
    void process(std::string name);    // OK - std::string was
forward-declared.

    using typename std::string;
    void process2(string name);    // Also OK

    namespace std {
        typename string;
    }
    void process3(std::string);    // Also OK


I think it could even work if typename is also used for declaring variable
of unknown type:

    typename ident;                                    // forward
declaration
    typedef typename ident new_name;     // alias (on forward declaration)

    typename T t;                                        // declaration - T
must be visible

    typename T t = f();                                // declare variable t
of type T to be compatible with f return type.
                                                                 //
(provided that no T type is visible at that scope).
    typename T t(f());                                 // idem

Maybe this would cause conflict with the current uses of typename... In that
case, maybe a check of visibility is enough (i.e. new meaning only if T not
seen before) or the syntax is not adequate... For the syntax, we may be able
to uses typename class T for the new uses to avoid any ambiguities.


By the way, I think that typename should be allowed for every type
declaration... and not just for template dependant stuff.


It would far more easier to reduce compile-time depedencies if  forward
declaration were more flexible. For example, although one could include
<iofwd> for stream forward declaration, we cannot do it the same for strings
(since there are no such header as <stringfwd>) and it does not apply to
user-type...

This would be a great plus to reduce dependencies without having to know the
exact type or include a bunch of files just to do the forward declaration.

Note that forward declaration should also be possible for nested type
(without needing the outer class definition).

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