Forward declaring names

358 views
Skip to first unread message

Magnus Fromreide

unread,
May 9, 2014, 6:15:52 PM5/9/14
to std-pr...@isocpp.org
Hello.

One thing that have annoyed me for a while is that I can't declare that a
name refers to a type unless I know if the name is a class-name or a
typedef-name when all I want to say is that the name is something I can form
a pointer or a reference to.

I can say

class foo;
void f1(foo&);

and I can also say

using bar = int;
void f2(bar&);

but I lack the ability to say

<typename or something> gaz;
void f3(gaz&);

where all that I tell the compiler is that gaz is a type, but don't have to
say if it is a typedef-name or a class-name.

Now, I do understand the reason for this since f2(bar&) should mangle like
f2(int&) so this suggests some kind of "strong" typedef that introduces a
new name for an existing type, and this is also an often requested feature.

Would it be feasible to introduce a strong typedef in order to allow general
forward declaration of types, or is this an utterly stupid idea?

The strong typedef should introduce a new type and place it in the same
namespace as class-names.

So the proposal is something along the lines of

// Declare two types, say nothing of what they contain

strong typedef aType;
strong typedef aClass;

// Actually define the types

strong typedef int aType; // possibly allow ommitting strong here?
class aClass { };

/MF

David Krauss

unread,
May 10, 2014, 1:41:59 AM5/10/14
to std-pr...@isocpp.org
On 2014–05–10, at 6:15 AM, Magnus Fromreide <ma...@lysator.liu.se> wrote:

The strong typedef should introduce a new type and place it in the same
namespace as class-names.

Is your intent to “bite off” a piece of the strong typedef proposal and implement it in anticipation of a more complete feature?

So the proposal is something along the lines of

// Declare two types, say nothing of what they contain

strong typedef aType;
strong typedef aClass;

// Actually define the types

strong typedef int aType; // possibly allow ommitting strong here?
class aClass { };

I don’t think transforming a typedef-name to a class-name should be allowed; you would need

strong typedef class aClass_class {
    …
} aClass;


I suspect your problem may be solved with metaprogramming, namely a template alias mapping from a set of tag classes (“strong typedef-names”) to a set of mapped types (which may or may not be classes) via a set of explicit specializations (“strong typedef declarations”). If that’s too much, simply organizing the forward declarations might do.

Magnus Fromreide

unread,
May 10, 2014, 12:41:34 PM5/10/14
to std-pr...@isocpp.org
On Sat, May 10, 2014 at 01:41:59PM +0800, David Krauss wrote:
>
> On 2014-05-10, at 6:15 AM, Magnus Fromreide <ma...@lysator.liu.se> wrote:
>
> > The strong typedef should introduce a new type and place it in the same
> > namespace as class-names.
>
> Is your intent to "bite off" a piece of the strong typedef proposal and
> implement it in anticipation of a more complete feature?

Not really - I was edging for the ability to forward declare the strong
typedef and n3741 explicitly forbids that.

> > So the proposal is something along the lines of
> >
> > // Declare two types, say nothing of what they contain
> >
> > strong typedef aType;
> > strong typedef aClass;
> >
> > // Actually define the types
> >
> > strong typedef int aType; // possibly allow ommitting strong here?
> > class aClass { };
>
> I don't think transforming a typedef-name to a class-name should be allowed;
> you would need
>
> strong typedef class aClass_class {
> ...
> } aClass;

Fair enough.

> I suspect your problem may be solved with metaprogramming, namely a
> template alias mapping from a set of tag classes ("strong typedef-names")
> to a set of mapped types (which may or may not be classes) via a set of
> explicit specializations ("strong typedef declarations"). If that's too
> much, simply organizing the forward declarations might do.

I also suspect that it could be done using template metaprogramming but that
have a tendency to end up beeing more complex than the original problem.

What I wanted to solve was to make it possible to say that 'aType' refers to
a type, but it is not yet specified which type it is.

I suppose this one could go in as an extension of n3741 but then the problem
is that the proposed syntax of n3741 prevents forward declaration of typedefs.

/MF

Jean-Marc Bourguet

unread,
May 10, 2014, 2:06:30 PM5/10/14
to std-pr...@isocpp.org
Le samedi 10 mai 2014 18:41:34 UTC+2, Magnus Fromreide a écrit :

One issue you don't seem to be aware of, if that to be useful, forward declarations of typedef implies that all pointers have the same representation. That's not the case currently for fundamental types.  That possibility has been used in C compilers for word addressable machines (people tend to forget the importance of such machines at the time C was developed, C was designed on a PDP-11, but the first ports were for the IBM360 and the Honeywell 635 --  word addressable, 36 bits words, 9 bits byte, but it was 2's complement). I don't know of a C++ implementation using the possibility -- my impression is that nowadays implementers prefer to use a char which has the word system on such systems -- but that's an area where C++ usually keep the C model.

Yours,

--
Jean-Marc

Magnus Fromreide

unread,
May 10, 2014, 2:56:07 PM5/10/14
to std-pr...@isocpp.org
On Sat, May 10, 2014 at 11:06:30AM -0700, Jean-Marc Bourguet wrote:
> Le samedi 10 mai 2014 18:41:34 UTC+2, Magnus Fromreide a écrit :
>
> One issue you don't seem to be aware of, if that to be useful, forward
> declarations of typedef implies that all pointers have the same
> representation. That's not the case currently for fundamental types.

Yes, you are right, and there are also function and member pointers for more
recent odd pointers so that kills the idea rather nicely.

Thanks.

/MF

Stack Machine

unread,
May 16, 2014, 5:41:22 PM5/16/14
to std-pr...@isocpp.org
Forward declarations are a stupid idea to begin with and should not be expanded on. The solution to this should be Modules.

Richard Smith

unread,
May 16, 2014, 6:36:44 PM5/16/14
to std-pr...@isocpp.org
On Fri, May 16, 2014 at 2:41 PM, Stack Machine <stackm...@hotmail.com> wrote:
Forward declarations are a stupid idea to begin with and should not be expanded on. The solution to this should be Modules.

I generally agree, but with just a pinch of caution: forward declarations serve two purposes: reducing include dependencies and breaking cycles. Modules only helps with the first problem; we still need forward declarations for the second problem.

gmis...@gmail.com

unread,
May 16, 2014, 7:16:17 PM5/16/14
to std-pr...@isocpp.org
How about:

auto HANDLE;
void (HANDLE& x); 

Meaning: "Hey compiler HANDLE might be a class, struct, typedef, or whatever, but if you don't need to know, just go with it knowing it's not a spelling mistake."

Is that what the OP wanted, and would it work?

Richard Smith

unread,
May 16, 2014, 7:37:23 PM5/16/14
to std-pr...@isocpp.org
On Fri, May 16, 2014 at 4:16 PM, <gmis...@gmail.com> wrote:

On Saturday, May 17, 2014 10:36:44 AM UTC+12, Richard Smith wrote:
On Fri, May 16, 2014 at 2:41 PM, Stack Machine <stackm...@hotmail.com> wrote:
Forward declarations are a stupid idea to begin with and should not be expanded on. The solution to this should be Modules.

I generally agree, but with just a pinch of caution: forward declarations serve two purposes: reducing include dependencies and breaking cycles. Modules only helps with the first problem; we still need forward declarations for the second problem.

How about:

auto HANDLE;

Something like 'typename HANDLE;' would probably be more appropriate, since this is a type, not a variable.

void (HANDLE& x); 

Meaning: "Hey compiler HANDLE might be a class, struct, typedef, or whatever, but if you don't need to know, just go with it knowing it's not a spelling mistake."

Is that what the OP wanted, and would it work?

I can't answer the first half of this question =)

Yes, this could work, but the type would need to be properly-declared before you could define or call this function, so it's not clear to me how useful this would be. (This level of opaqueness goes beyond being a merely incomplete type, since you don't even know how to represent a pointer or reference to the type.)

Magnus Fromreide

unread,
May 16, 2014, 9:22:29 PM5/16/14
to std-pr...@isocpp.org
On Fri, May 16, 2014 at 04:16:17PM -0700, gmis...@gmail.com wrote:
>
> On Saturday, May 17, 2014 10:36:44 AM UTC+12, Richard Smith wrote:
> >
> > On Fri, May 16, 2014 at 2:41 PM, Stack Machine <stackm...@hotmail.com<javascript:>
> > > wrote:
> >
> >> Forward declarations are a stupid idea to begin with and should not be
> >> expanded on. The solution to this should be Modules.
> >>
> >
> > I generally agree, but with just a pinch of caution: forward declarations
> > serve two purposes: reducing include dependencies and breaking cycles.
> > Modules only helps with the first problem; we still need forward
> > declarations for the second problem.
> >
>
> How about:
>
> auto HANDLE;
> void (HANDLE& x);
>
> Meaning: "Hey compiler HANDLE might be a class, struct, typedef, or
> whatever, but if you don't need to know, just go with it knowing it's not a
> spelling mistake."
>
> Is that what the OP wanted,

Yes.

> and would it work?

No - see the original thread. Some pointers and references ain't like other
pointers and references. Examples include member pointers.

/MF

Thiago Macieira

unread,
May 16, 2014, 10:27:03 PM5/16/14
to std-pr...@isocpp.org
Em sex 16 maio 2014, às 16:16:17, gmis...@gmail.com escreveu:
> How about:
>
> auto HANDLE;
> void (HANDLE& x);
>
> Meaning: "Hey compiler HANDLE might be a class, struct, typedef, or
> whatever, but if you don't need to know, just go with it knowing it's not a
> spelling mistake."
>
> Is that what the OP wanted, and would it work?

Can you enumerate what those cases "without knowing" you'd like the standard
to support?

You can rule out:
- calling a function taking HANDLE by value, by pointer or by reference
- using HANDLE in a template parameter
- declaring a variable of type cv-qualified HANDLE, HANDLE* or HANDLE&
- using HANDLE in a structure

What's left?
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

gmis...@gmail.com

unread,
May 17, 2014, 11:01:44 PM5/17/14
to std-pr...@isocpp.org
Hi


On Saturday, May 17, 2014 2:27:03 PM UTC+12, Thiago Macieira wrote:
Em sex 16 maio 2014, às 16:16:17, gmis...@gmail.com escreveu:
> How about:
>
> auto HANDLE;
> void (HANDLE& x);
>
> Meaning: "Hey compiler HANDLE might be a class, struct, typedef, or
> whatever, but if you don't need to know, just go with it knowing it's not a
> spelling mistake."
>
> Is that what the OP wanted, and would it work?

Can you enumerate what those cases "without knowing" you'd like the standard
to support?

I'm not sure, that's why I was asking would it work. I was just querying if that was the kind of open declaration the OP was looking for.

You can rule out:
 - calling a function taking HANDLE by value, by pointer or by reference
 - using HANDLE in a template parameter
 - declaring a variable of type cv-qualified HANDLE, HANDLE* or HANDLE&
 - using HANDLE in a structure

What's left?

I don't see that anything is left. I have sometimes wanted to say "hey I don't know if this is a "struct or a class name, but this is a pointer or reference to that". But that was being lazy. It seems the goal of the OP was something like that.

Farid Mehrabi

unread,
May 18, 2014, 9:17:22 AM5/18/14
to std-proposals


2014-05-10 23:26 GMT+04:30 Magnus Fromreide <ma...@lysator.liu.se>:
Yes, you are right, and there are also function and member pointers for more
recent odd pointers so that kills the idea rather nicely.

I should respectfully object that this one is irrelevant. Normal pointers have the same size and representation - regarding the compilation platform. And they are implicitly statically cast-able to void*; static_cast back from this result to the original type is also well-defined behavior. 

In contrast to normal pointers, member pointers have diverse sizes and representations even on the same platform -regarding their type. These types are not statically cast-able to void* and casting from void* to such types is Undefined-Behavior. 

In short words : the only common feature between member pointers and normal pointers is the term pointer while they are 2 different categories of built-in types.

Back to the OT: I think from a library creator sight this could be good idea for hiding the implementation of a type from the final programmer. The type is to be used in either ref or pointer form in user code and  the client program does not need to know anything about its memory layout.

Side note : Someone else used the term 'stupid'; whether or not some idea looks practical to SB, the term used in that post  does not sound respectful to - maybe only - me.

regards,
FM.

--
how am I supposed to end the twisted road of  your hair in the dark night??
unless the candle of your face does turn a lamp up on my way!!!

Stack Machine

unread,
May 18, 2014, 10:06:09 AM5/18/14
to std-pr...@isocpp.org
I don't see how cycles are a problem at all. Other languages don't need forward declarations either. I'm sure they've come up with some viable solutions.

Farid Mehrabi

unread,
May 18, 2014, 12:06:11 PM5/18/14
to std-proposals
Cyclic referencing types? with header+module design of C++ I can not imagine any better solution than forward declarations. If cyclic refers to recursion, then I can see no better solution than forward declaration in any strongly typed language. 

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Magnus Fromreide

unread,
May 18, 2014, 1:37:11 PM5/18/14
to std-pr...@isocpp.org
On Sun, May 18, 2014 at 05:47:22PM +0430, Farid Mehrabi wrote:
> 2014-05-10 23:26 GMT+04:30 Magnus Fromreide <ma...@lysator.liu.se>:
>
> > Yes, you are right, and there are also function and member pointers for
> > more
> > recent odd pointers so that kills the idea rather nicely.
> >
>
> I should respectfully object that this one is irrelevant. Normal pointers
> have the same size and representation - regarding the compilation platform.
> And they are implicitly statically cast-able to void*; static_cast back
> from this result to the original type is also well-defined behavior.

What about far, near and huge pointers on dos?
What about char pointers on word-addressable machines?

> In contrast to normal pointers, member pointers have diverse sizes and
> representations even on the same platform -regarding their type. These
> types are not statically cast-able to void* and casting from void* to such
> types is Undefined-Behavior.
>
> In short words : the only common feature between member pointers and normal
> pointers is the term pointer while they are 2 different categories of
> built-in types.
>
> Back to the OT: I think from a library creator sight this could be good
> idea for hiding the implementation of a type from the final programmer. The
> type is to be used in either ref or pointer form in user code and the
> client program does not need to know anything about its memory layout.

That was the use I intended for them :-)

> Side note : Someone else used the term 'stupid'; whether or not some idea
> looks practical to SB, the term used in that post does not sound
> respectful to - maybe only - me.

Yes. That was me, referring to my own idea; one can't use such language when
talking about the ideas of others.
I am sorry if I offended you.

/MF

Richard Smith

unread,
May 18, 2014, 2:58:07 PM5/18/14
to std-pr...@isocpp.org
On Sun, May 18, 2014 at 6:17 AM, Farid Mehrabi <farid....@gmail.com> wrote:



2014-05-10 23:26 GMT+04:30 Magnus Fromreide <ma...@lysator.liu.se>:
Yes, you are right, and there are also function and member pointers for more
recent odd pointers so that kills the idea rather nicely.

I should respectfully object that this one is irrelevant. Normal pointers have the same size and representation - regarding the compilation platform.

This is true on common platforms, but not in general, and the standard has no such requirement.
 
And they are implicitly statically cast-able to void*; static_cast back from this result to the original type is also well-defined behavior. 

Yes, void* must be able to represent any object pointer, but some pointer types might be smaller than void*. For instance, on a word-addressable machine, the ABI might say that all class types have a size that is a multiple of one word, and all class pointers are the "normal" size for the machine, but char* and void* are represented as a pair of (word pointer, byte index).

Richard Smith

unread,
May 18, 2014, 2:59:38 PM5/18/14
to std-pr...@isocpp.org
On Sun, May 18, 2014 at 7:06 AM, Stack Machine <stackm...@hotmail.com> wrote:
I don't see how cycles are a problem at all. Other languages don't need forward declarations either. I'm sure they've come up with some viable solutions.

Other languages that do without forward declarations (such as Java) are parseable without knowing what identifiers refer to types and non-types. C++ is not; this solution is not viable for C++.

Am Samstag, 17. Mai 2014 00:36:44 UTC+2 schrieb Richard Smith:
On Fri, May 16, 2014 at 2:41 PM, Stack Machine <stackm...@hotmail.com> wrote:
Forward declarations are a stupid idea to begin with and should not be expanded on. The solution to this should be Modules.

I generally agree, but with just a pinch of caution: forward declarations serve two purposes: reducing include dependencies and breaking cycles. Modules only helps with the first problem; we still need forward declarations for the second problem.

--

Farid Mehrabi

unread,
May 19, 2014, 11:05:03 AM5/19/14
to std-proposals
2014-05-18 22:07 GMT+04:30 Magnus Fromreide <ma...@lysator.liu.se>:
On Sun, May 18, 2014 at 05:47:22PM +0430, Farid Mehrabi wrote:
> 2014-05-10 23:26 GMT+04:30 Magnus Fromreide <ma...@lysator.liu.se>:
>
> > Yes, you are right, and there are also function and member pointers for
> > more
> > recent odd pointers so that kills the idea rather nicely.
> >
>
> I should respectfully object that this one is irrelevant. Normal pointers
> have the same size and representation - regarding the compilation platform.
> And they are implicitly statically cast-able to void*; static_cast back
> from this result to the original type is also well-defined behavior.

What about far, near and huge pointers on dos?


they are different families of pointers on the same platform. And far/near are not properties of the  referenced type. An object pointer is still cast-able to&from void* of the same family - irrelevant of the specifier used to declare the type(struct,class,enum ...).

regards,
FM.

 
What about char pointers on word-addressable machines?

> Side note : Someone else used the term 'stupid'; whether or not some idea
> looks practical to SB, the term used in that post  does not sound
> respectful to - maybe only - me.

Yes. That was me, referring to my own idea; one can't use such language when
talking about the ideas of others.
I am sorry if I offended you.

 Oops! I should call myself the same.

Farid Mehrabi

unread,
May 19, 2014, 11:08:55 AM5/19/14
to std-proposals
2014-05-18 23:28 GMT+04:30 Richard Smith <ric...@metafoo.co.uk>:
On Sun, May 18, 2014 at 6:17 AM, Farid Mehrabi <farid....@gmail.com> wrote:



2014-05-10 23:26 GMT+04:30 Magnus Fromreide <ma...@lysator.liu.se>:
Yes, you are right, and there are also function and member pointers for more
recent odd pointers so that kills the idea rather nicely.

I should respectfully object that this one is irrelevant. Normal pointers have the same size and representation - regarding the compilation platform.

This is true on common platforms, but not in general, and the standard has no such requirement.
 

I used to consider this as the de facto  standard.

And they are implicitly statically cast-able to void*; static_cast back from this result to the original type is also well-defined behavior. 

Yes, void* must be able to represent any object pointer, but some pointer types might be smaller than void*. For instance, on a word-addressable machine, the ABI might say that all class types have a size that is a multiple of one word, and all class pointers are the "normal" size for the machine, but char* and void* are represented as a pair of (word pointer, byte index).

 
I need a concrete practical example please. PC mainframe micro?? 
A machine + OS + tool chain + relative pointer-sizes .

regards,
FM.
 

Farid Mehrabi

unread,
May 19, 2014, 11:26:40 AM5/19/14
to std-proposals
Even if any platform with such design exists, the forward declared type can use pointers/references with void* layout. This is sub-optimal but yet easily implementable.

And IMHO the 'typename' keyword is a good candidate for the purpose. It clearly expresses the intention.

Jean-Marc Bourguet

unread,
May 19, 2014, 11:42:23 AM5/19/14
to std-pr...@isocpp.org
Le lundi 19 mai 2014 08:08:55 UTC-7, Farid Mehrabi a écrit :

2014-05-18 23:28 GMT+04:30 Richard Smith <ric...@metafoo.co.uk>:
On Sun, May 18, 2014 at 6:17 AM, Farid Mehrabi <farid....@gmail.com> wrote:

This is true on common platforms, but not in general, and the standard has no such requirement.
 

I used to consider this as the de facto  standard.

But we are in a group discussing proposals for the formal standard.
 
And they are implicitly statically cast-able to void*; static_cast back from this result to the original type is also well-defined behavior. 

Yes, void* must be able to represent any object pointer, but some pointer types might be smaller than void*. For instance, on a word-addressable machine, the ABI might say that all class types have a size that is a multiple of one word, and all class pointers are the "normal" size for the machine, but char* and void* are represented as a pair of (word pointer, byte index).

 
I need a concrete practical example please. PC mainframe micro?? 
A machine + OS + tool chain + relative pointer-sizes .

I know of none.  I'm pretty sure it is allowed because it was the case for some C implementations (mainframe or embedded systems are the suspects, I know again none, the two unusual implementations for C I know which target word-addressable machines -- KCC for PDP-10 and unysis C compiler for OS 2200 -- are using the same pointer size for all data pointers). 

You could propose to make what you think the de factor standard official, but this is an area where the C++ committee usually defers to the C one.

Yours,

Jean-Marc Bourguet

unread,
May 19, 2014, 11:45:35 AM5/19/14
to std-pr...@isocpp.org
Le lundi 19 mai 2014 08:26:40 UTC-7, Farid Mehrabi a écrit :





Even if any platform with such design exists, the forward declared type can use pointers/references with void* layout. This is sub-optimal but yet easily implementable.


I don't see how that would work when calling functions whose definition don't see the forward declaration.

Yours,

-- 
Jean-Marc

Thiago Macieira

unread,
May 19, 2014, 12:35:20 PM5/19/14
to std-pr...@isocpp.org
Em seg 19 maio 2014, às 19:38:55, Farid Mehrabi escreveu:
> > Yes, void* must be able to represent any object pointer, but some pointer
> > types might be smaller than void*. For instance, on a word-addressable
> > machine, the ABI might say that all class types have a size that is a
> > multiple of one word, and all class pointers are the "normal" size for the
> > machine, but char* and void* are represented as a pair of (word pointer,
> > byte index).
>
> I need a concrete practical example please. PC mainframe micro??
> A machine + OS + tool chain + relative pointer-sizes .

Let me throw another wrench in this discussion:

even if you could determine the bitwise composition of the pointer or
reference of this forward declared typename, you can't call a function with it
and you can't use it in a template expansion. Some ABIs require encoding the
aggregate type (class, struct, enum), and most ABIs will encode the original,
non-typedef'ed name.

Farid Mehrabi

unread,
May 20, 2014, 11:00:27 AM5/20/14
to std-proposals
where should those functions be declared if they are intended for public use? how can an undeclared function  be used in c++ without generating any sort of compile time diagnostics?

if a function is a part of public interface of the type, it should be declared in the same header that declares the type and that header is 
included in the defining module of the interface as well as client modules. smart cautious use of include directives can help optimize away the overhead of long pointer size - In case hypothetically  such an overhead is expected on a platform - in private modules whose functions are not to be published for public use.

Farid Mehrabi

unread,
May 20, 2014, 11:07:48 AM5/20/14
to std-proposals
2014-05-19 21:05 GMT+04:30 Thiago Macieira <thi...@macieira.org>:
Em seg 19 maio 2014, às 19:38:55, Farid Mehrabi escreveu:
> > Yes, void* must be able to represent any object pointer, but some pointer
> > types might be smaller than void*. For instance, on a word-addressable
> > machine, the ABI might say that all class types have a size that is a
> > multiple of one word, and all class pointers are the "normal" size for the
> > machine, but char* and void* are represented as a pair of (word pointer,
> > byte index).
>
> I need a concrete practical example please. PC mainframe micro??
> A machine + OS + tool chain + relative pointer-sizes .

Let me throw another wrench in this discussion:

even if you could determine the bitwise composition of the pointer or
reference of this forward declared typename, you can't call a function with it
and you can't use it in a template expansion. Some ABIs require encoding the
aggregate type (class, struct, enum), and most ABIs will encode the original,
non-typedef'ed name.


 
will encode ? ?
no compiler is currently implementing this syntax, that`s the purpose of this post: we want future std compilers to be able to do it and we are discussing how to.
The general forward declaration syntax (the one with generic specifier 'typename') should instruct the compiler to choose the most conservative approach. I don`t how that`s gonna make any troubles.Unless you don`t use the value semantics compiler does not complain. the pointer/reference has proper memory layout.

regards,
FM. 

Thiago Macieira

unread,
May 20, 2014, 11:58:11 AM5/20/14
to std-pr...@isocpp.org
Em ter 20 maio 2014, às 19:37:48, Farid Mehrabi escreveu:
> > even if you could determine the bitwise composition of the pointer or
> > reference of this forward declared typename, you can't call a function
> > with it
> > and you can't use it in a template expansion. Some ABIs require encoding
> > the
> > aggregate type (class, struct, enum), and most ABIs will encode the
> > original,
> > non-typedef'ed name.
>
> will encode ? ?

They will encode because they do right now.

> no compiler is currently implementing this syntax, that`s the purpose of
> this post: we want future std compilers to be able to do it and we are
> discussing how to.

Indeed. But please understand you're asking every single compiler to break
their ABI. That is, to enable support for this feature, no program previously
compiled will link with new code.

> The general forward declaration syntax (the one with generic specifier
> 'typename') should instruct the compiler to choose the most conservative
> approach. I don`t how that`s gonna make any troubles.Unless you don`t use
> the value semantics compiler does not complain. the pointer/reference has
> proper memory layout.

Right now, if you do like this:

class Class;
struct Struct;
typedef Struct T;

void f(Class *, Struct *);
void g(T *);

The external name of f and g will include some information that this proposal
makes impossible to know.

IA-64 mangling for g():
_Z1g P6Struct (space mine, note the typedef was piereced)

MSVC mangling for both:
?f@@YAX PAVClass@@ PAUStruct@@ @Z
?g@@YAX PAUStruct@@ @Z
(space mine, note the U before Struct and V before Class)

If the code above were:

typename Class;
typename Struct;
typename T;

Then how would the compiler know that Class is a class and Struct is a struct,
and that T is the same as Struct?

Farid Mehrabi

unread,
May 20, 2014, 12:15:09 PM5/20/14
to std-proposals
First, let the mangling to the compiler producers.

the purpose is that the compiler does not care what 'T' or 'Class' ... is, unless in a library private module. Anywhere else it only need know that T* is a pointer to some unknown type.
take a look at classic 'FILE' type in standard C lib docs, and sqlite lib for example. the client code does not care what 'FILE' or 'sqlite' is; 
it only deals with FILE* or sqlite* . The definition is buried deeply inside the private library modules which maybe not available in source form to the application programmer. declaring such types with typename will not change the semantics but rather relaxes the library programmer from early decision about aspects that might change in future versions of the library. The idea is to hide as much unnecessary details from application programmer as possible.

regards,
FM.

 
--

Thiago Macieira

unread,
May 20, 2014, 12:37:47 PM5/20/14
to std-pr...@isocpp.org
Em ter 20 maio 2014, às 20:45:09, Farid Mehrabi escreveu:
> First, let the mangling to the compiler producers.

And to standard writers who make changes that affect ABI. Standard developers
need at least to know that the feature they're proposing can be implemented.

> the purpose is that the compiler does not care what 'T' or 'Class' ... is,
> unless in a library private module. Anywhere else it only need know that T*
> is a pointer to some unknown type.

You do realise that you're asking that C++ drop backwards compatibility,
right?

That is, if I have an existing library already compiled and I want to use it
in new code, I need to know what T is. Relaxing this requirement means the
compiler breaks compatibility.

Sure, the standard does know about libraries. But real life does. This feature
is major and invasive and it will unreasonably break existing code.

> take a look at classic 'FILE' type in standard C lib docs, and sqlite lib
> for example. the client code does not care what 'FILE' or 'sqlite' is;
> it only deals with FILE* or sqlite* . The definition is buried deeply
> inside the private library modules which maybe not available in source form
> to the application programmer. declaring such types with typename will not
> change the semantics but rather relaxes the library programmer from early
> decision about aspects that might change in future versions of the library.
> The idea is to hide as much unnecessary details from application programmer
> as possible.

Unfortunately, that's not the case. The compiler does care what FILE is
because it gets mangled in the C++ external name. (with glibc, FILE is a
typedef to struct _IO_FILE; on OS X, FILE is typedef to struct __sFILE)

Matthew Woehlke

unread,
May 20, 2014, 5:21:57 PM5/20/14
to std-pr...@isocpp.org
On 2014-05-18 09:17, Farid Mehrabi wrote:
> Back to the OT: I think from a library creator sight this could be good
> idea for hiding the implementation of a type from the final programmer. The
> type is to be used in either ref or pointer form in user code and the
> client program does not need to know anything about its memory layout.

class Private;
void foo(Private*);

...still works AFAIK.

A better argument is that it's convenient to the API (and *ABI*) to be
able to declare "Private" without specifying if it is a class, struct,
or typedef. (This drives me nuts on occasion, where I have to include a
header that defines some typedef that is only ever referenced as a
pointer/reference/return, just because it *is* a typedef and so can't be
forward declared. Or worse, because it may or may not be a typedef,
depending on the platform.)

Of course the "gotcha" (as Thiago notes) is that the compiler won't know
how to mangle these if it never sees a definition (unless they're
"strong", but then the TU that emits the symbol using the typename would
also have to know that it's "strong", or the mangled names won't match
and will result in link errors). Though as implied by Farid, this may
rarely be a problem in practice.

I'm not convinced the alleged ABI breakage is as much of an issue as is
being stated, since the ABI only changes for users of the feature.
Presumably these are either brand new ABI's with nothing old to break,
or else the library authors that choose to switch to using the new
feature know what they are doing.

--
Matthew

Thiago Macieira

unread,
May 20, 2014, 7:58:35 PM5/20/14
to std-pr...@isocpp.org
Em ter 20 maio 2014, às 17:21:57, Matthew Woehlke escreveu:
> I'm not convinced the alleged ABI breakage is as much of an issue as is
> being stated, since the ABI only changes for users of the feature.
> Presumably these are either brand new ABI's with nothing old to break,
> or else the library authors that choose to switch to using the new
> feature know what they are doing.

That only works if the new opaque definition exists for everyone and is used
everywhere.

That is, if we have:

typename Foo;
typename Bar::type;
typename Baz;
typename FooBar;

Then *everyone* needs to see the typename, even those TUs that expand it
later:

// required even if "typename Bar" wasn't previously seen,
// due to typename Bar::type
typename struct Bar { typedef int type; };

typename class Foo : public Bar {};
typename class enum Baz { Frob = 0 };
typename typedef Foo FooBar;

Either the previous typename must be present or the extra keyword must.
Otherwise, the program is ill-formed. In this, it behaves like the noexcept
keyword.

Also note that typename typedef should only be allowed if opaque typedefs have
been standardised and their behaviour is well understood.

And finally, note the duplication of the typename keyword:

typename typedef typename ADL<T>::type type;

Farid Mehrabi

unread,
May 21, 2014, 10:39:13 AM5/21/14
to std-proposals
2014-05-21 4:28 GMT+04:30 Thiago Macieira <thi...@macieira.org>:
Em ter 20 maio 2014, às 17:21:57, Matthew Woehlke escreveu:
> I'm not convinced the alleged ABI breakage is as much of an issue as is
> being stated, since the ABI only changes for users of the feature.
> Presumably these are either brand new ABI's with nothing old to break,
> or else the library authors that choose to switch to using the new
> feature know what they are doing.

That only works if the new opaque definition exists for everyone and is used
everywhere.

That is, if we have:

        typename Foo;
        typename Bar::type;
        typename Baz;
        typename FooBar;

Then *everyone* needs to see the typename, even those TUs that expand it
later:

        // required even if "typename Bar" wasn't previously seen,
        // due to typename Bar::type
        typename struct Bar { typedef int type; };

        typename class Foo : public Bar {};
        typename class enum Baz { Frob = 0 };
  
      typename typedef Foo FooBar;


Noooooooo:
///////////////////////////Public.h//////////////////////////
typename Private;
void PublicFoo(Private&);

//////////////////////////Private.h//////////////////////////
typedef XXXX Private;

//////////////////////////Private.c//////////////////////////
/* Output -> Private.a, Private.lib , Private.so, Private.dll*/
#include <Public.h>
#include <Private.h>
void PublicFoo(Private& ref)
{
      //blah,blah....
};

///////////////////////////User.c/////////////////////////////
#include <Public.h>
void  UserFoo(Private* ptr){PublicFoo(*ptr);};


Thiago Macieira

unread,
May 21, 2014, 11:15:54 AM5/21/14
to std-pr...@isocpp.org
Em qua 21 maio 2014, às 19:09:13, Farid Mehrabi escreveu:
> ///////////////////////////Public.h//////////////////////////
> typename Private;
> void PublicFoo(Private&);
>
> //////////////////////////Private.h//////////////////////////
> typedef XXXX Private;
>
> //////////////////////////Private.c//////////////////////////
> /* Output -> Private.a, Private.lib , Private.so, Private.dll*/
> #include <Public.h>
> #include <Private.h>

As long as that #include <Public.h> is present, you're still matching my
request.

> void PublicFoo(Private& ref)
> {
> //blah,blah....
> };
>
> ///////////////////////////User.c/////////////////////////////
> #include <Public.h>
> void UserFoo(Private* ptr){PublicFoo(*ptr);};

The above translates to:

=== private.cpp ===
typename Private;
typedef XXXX Private;
void PublicFoo(Private &ref) {}

=== public.cpp ===
typename Private;
void PublicFoo(Private &);
void UserFoo(Private *ptr) { PublicFoo(*ptr); }
====

The above is could work.

However, if you remove the first line in the private source and make it like
so:

=== private.cpp ===
typedef XXXX Private;
void PublicFoo(Private &ref) {}

====

Then the program is ill-formed.

Farid Mehrabi

unread,
May 21, 2014, 1:08:04 PM5/21/14
to std-proposals
That matters only If the layout of pointer is type-dependent. Name mangling issues can be fixed by some sort of regexp magic in the linker design to be able to match correct definitions. That sort of trick is already used in embedded industry to enable optional definition of e.g interrupt handler functions; if the user provides a definition for certain functions, the default definition is rejected.
 

Thiago Macieira

unread,
May 21, 2014, 2:04:53 PM5/21/14
to std-pr...@isocpp.org
Em qua 21 maio 2014, às 21:38:04, Farid Mehrabi escreveu:
> > === public.cpp ===
> > typename Private;
> > void PublicFoo(Private &);
> > void UserFoo(Private *ptr) { PublicFoo(*ptr); }
> > ====
[snip]
> > However, if you remove the first line in the private source and make it
> > like
> > so:
> >
> > === private.cpp ===
> > typedef XXXX Private;
> > void PublicFoo(Private &ref) {}
> >
> > ====
> >
> > Then the program is ill-formed.
>
> That matters only If the layout of pointer is type-dependent. Name mangling
> issues can be fixed by some sort of regexp magic in the linker design to be
> able to match correct definitions. That sort of trick is already used in
> embedded industry to enable optional definition of e.g interrupt handler
> functions; if the user provides a definition for certain functions, the
> default definition is rejected.

I suggest you reconsider.

Symbols aren't matched just at the traditional link time. Run-time linking is
very important and performance-critical. Doing fuzzy matches at runtime is
going to incur non-trivial cost.

And besides, I don't think it's even possible. Suppose we have the source file
above compiled into a library. Since the source didn't list "typename Private"
anywhere, the typedef is a traditional one, from the depths of C++ history
(not an opaque one). As such, the source file has a symbol for:
"void PublicFoo(XXXX &)"

just to make matters a little more complex, let's say there's an overload:
"void PublicFoo(YYYY &)"

Now we have the public.cpp source compiled with the new feature, which is
requesting the symbol:
"void PublicFoo(Private &)"

Please explain which algorithm will allow the compiler, linker and/or dynamic
linker to realise that Private == XXXX.

Matthew Woehlke

unread,
May 21, 2014, 2:32:39 PM5/21/14
to std-pr...@isocpp.org
On 2014-05-21 14:04, Thiago Macieira wrote:
> Please explain which algorithm will allow the compiler, linker and/or dynamic
> linker to realise that Private == XXXX.

Are symbol aliases allowed?

What if we just exported the symbol under all possible names?

IOW, given:

class foo;
using bar = foo;
typename bar;

void my(bar&);

...we would have symbol aliases for both my(typename bar) and my(class
foo), which would be the same entry point.

--
Matthew

Magnus Fromreide

unread,
May 22, 2014, 2:49:12 AM5/22/14
to std-pr...@isocpp.org
Would

/// Public.hpp
struct Private;
void PublicFoo(Private&);

/// Private.hpp
struct Private { XXXX value; };

/// Private.cpp
/* Output -> Private.a, Private.lib , Private.so, Private.dll*/
#include <Public.h>
#include <Private.h>
void PublicFoo(Private& ref)
{
//blah,blah...., but with ref.value rather than ref.
};

/// User.c
include <Public.h>
void UserFoo(Private* ptr){PublicFoo(*ptr);}

really be bad enough to warrant a change to the standard that have great
potential to affect the various ABIs.

The lesson from this is to never ever use typedefs in interfaces and allow
forward declaration of non-template structs from the std namespace.

Thus

namespace std { struct string : basic_string<char> { using basic_string; }; }

instead of

namespace std { using string = std::basic_string<char>; }

would have saved immense amounts of preprocessing time as the former is
trivial to forward declare while the latter requires a declaration of
basic_string to forward declare.

Yes, I see why the standard isn't written like this, with the "good" form
using C++11 features or reeated constructors, but it would have been better.

/MF

walter1234

unread,
Jun 15, 2014, 8:15:21 AM6/15/14
to std-pr...@isocpp.org
I like this idea.
Would it be possible to use 'typename' for this?

typename foo;
void Bar(foo& .. );

// would that also be useful for disambiguating parse information in some contexts, 
// e.g. you now know  "foo *a;" is a variable declaration not a call to operator *, without having parsed the full definition of foo from a header yet. 
Reply all
Reply to author
Forward
0 new messages