Since quite a few people have brought up fvali's post ( cf. http://www.deja.com/=rj/[ST_rn=ap]/getdoc.xp?AN=451759382 ) from March 5 responding to Andrei's question about how to determine whether a class argument of a template has a certain member, I thought I would take a look at it. fvali came up with an absolutely brilliant, fully standard conformant way of coming very close to doing this. The exact code that fvali posted won't compile because he has an illegal definition that appears to be left over from an earlier implementation. Also, once fixed, it can only determine whether a class has a specified inner type. It's unable to handle member data or member functions.
His code is definitely hard to understand and it's a little more complicated than it needs to be. Here's a simpler re-implementation of his idea:
class unique {}; template<class _Typ, class _Uq = unique > class key_type_tester : private _Typ { struct nested; friend struct nested; typedef char (& yes)[1]; typedef char (& no)[2]; static no test(_Uq); static yes test(...);
};
template< class _Typ, class key_type> struct key_type_tester<_Typ, key_type>::nested { enum { has_key_type= sizeof(test(key_type())) != sizeof(no) };
};
Given this, key_type_tester<T>::nested::has_key_type is a compile time constant which is true it T has an inner type named key_type and false if it doesn't (well, almost ...). The way it works depends on 14.6.1 Paragraph 5 of the standard which is worth requoting here:
5 In the definition of a member of a class template that appears outside of the class template definition, the name of a member of this tem- plate hides the name of a template-parameter. [Example: template<class T> struct A { struct B { /* ... */ }; void f(); };
template<class B> void A<B>::f() { B b; // A's B, not the template parameter } --end example]
Because the definition of nested occurs outside the class template definition, any members of the key_type_tester template (which, crucially, includes all of _Typ's members) which have the name key_type, hide the template parameter named key_type. So, in the expression test(key_type()), it is either unique's constructor being referenced, or an inner type of _Typ named key_type if one exists. Since test() is overloaded, one version accepting unique objects and the other accepting anything else, if _Typ doesn't have a key_type, "static no test(_Uq)" is referenced, otherwise "static yes test(...)" is. Since these two functions return different sized types, sizeof lets us determine which is actually referenced.
There are (at least) 2 cases where this code will produce compile time errors:
1. There is a non-type, non-static member of _Typ named key_type. 2. There is no default constructor for _Typ's key_type. 3. There is an inaccessible (private) member of _Typ named key_type.
As far as I can tell (1) is unavoidable. (2) can be fixed by making sizeof(unique) effectively unique (e.g. class unique {char u[5431];};) and then modifying the enum to: enum { has_key_type= sizeof(key_type) != sizeof(unique) };
The rest of the template apparatus that fvali uses achieves 2 things that are different than my implementation:
1. The unique class is a private member of the key_type_tester template. 2. references to the compile time constant do not need the "nested" scope modifier.
All of this is helpful, but what is really needed is a way to determine whether a type has a certain member. I worked out a way to do this for member functions, but I now have this horrible suspicion that it is not standard conformant. Anyway, here it is since I've already put it together :-).
There are two ways to do it. You can either test for a member function with a certain name having any signature or for one having a specific signature. The following tests code test for the first case:
Note that in the second case, dummy must default to a non-zero value otherwise it will implicitly convert to the member function pointer.
This code works on the Intel Compiler 4.0 (which uses the EDG front-end), but as I mentioned, I don't think it conforms to the standard. It depends on "memfun" referring to a pointer to a member function. However, the standard says in 5.3.1p3, "A pointer to a member is only formed when an explicit & is used and its operand is a qualified-id not enclosed in parentheses." This sounds pretty unequivocal :(. Many compilers, it seems, do not conform on this point, but, obviously, that's not something that can be trusted. If it doesn't work then this name hiding technique cannot be used to determine whether a class has certain member functions. I think that I can actually prove this, but I'm tired of typing at the moment :). (Note that the type member test still works - except in the odd cases I've mentioned).
Hope this helps -- and if anyone has any ideas, please post them.
John Madsen
jmadsen at rci dot rutgers dot edu
[ Send an empty e-mail to c++-h...@netlab.cs.rpi.edu for info ] [ about comp.lang.c++.moderated. First time posters: do this! ]
template< class _Typ, class key_type> struct key_type_tester<_Typ, key_type>::nested { enum { has_key_type= sizeof(test((key_type*)0)) != sizeof(no) };
};
I take advantage that the type 'int' lacks a key_type member :o).
The code became a bit even easier to understand. I think this is a breakthrough in what one can do with C++. It brings a bit of introspection at compile time possible, which wasn't even considered possible until now. I also think that techniques based upon overloading, sizeof, and char[1] and char[2] are going to become common in the next century (ahem). They have a lot of uses you wouldn't think of.
Ok, now we've got the ability to find out whether an arbitrary class has a member type of a given name. What are we going to do with this?... :o)
Andrei
[ Send an empty e-mail to c++-h...@netlab.cs.rpi.edu for info ] [ about comp.lang.c++.moderated. First time posters: do this! ]
"Andrei Alexandrescu" <andrewa...@hotmail.com> wrote: >John Madsen <jmad...@rci.rutgers.edu> wrote in message >news:382f67af.1293831471@news.supernews.com... >> fvali came up with an absolutely brilliant, fully >> standard conformant way of coming very close to doing this.
>I second you on this. And I think the same about yourself.
That's very flattering, but fvali did the real work :).
>template< class _Typ, class key_type> >struct key_type_tester<_Typ, key_type>::nested { > enum { has_key_type= sizeof(test((key_type*)0)) != sizeof(no) }; >};
>I take advantage that the type 'int' lacks a key_type member :o).
This will certainly get around the compile time error, but you will get a false negative in the case where _Typ's key_type is actually an int. This may or may not be a problem depending on the problem at hand. Also, nowhere does this code depend on key_type either being or not being a member of int as far as I can tell.
>The code became a bit even easier to understand. I think this is a >breakthrough in what one can do with C++. It brings a bit of >introspection at compile time possible, which wasn't even considered >possible until now. I also think that techniques based upon >overloading, sizeof, and char[1] and char[2] are going to become >common in the next century (ahem). They have a lot of uses you >wouldn't think of.
>Ok, now we've got the ability to find out whether an arbitrary class >has a member type of a given name. What are we going to do with >this?... :o)
I agree. I think fvalia has hit on something that could be useful in ways we have yet to realize. However, unless there is portable way of determining whether a type has a certain member function, I'm not sure how useful all of this will be. That's why I'm holding out for finding something in the standard that contradicts 5.3.1p3 -- it wouldn't be the first contradiction in the standard :). I think there must be because every compiler I've tried (now including KAI C++) will convert an unqualified member function name into a pointer to member function within class scope. I'm hoping a standard guru can clear this up :).
John Madsen
jmadsen at rci dot rutgers dot edu
[ Send an empty e-mail to c++-h...@netlab.cs.rpi.edu for info ] [ about comp.lang.c++.moderated. First time posters: do this! ]
In article <382f27a7.168342...@news.supernews.com>, jmad...@rci.rutgers.edu (John Madsen) wrote: [...]
> I agree. I think fvalia has hit on something that could be useful in ways > we have yet to realize. However, unless there is portable way of > determining whether a type has a certain member function, I'm not sure how > useful all of this will be. That's why I'm holding out for finding > something in the standard that contradicts 5.3.1p3 -- it wouldn't be the > first contradiction in the standard :). I think there must be because > every compiler I've tried (now including KAI C++) will convert an > unqualified member function name into a pointer to member function within > class scope. I'm hoping a standard guru can clear this up :).
I tried the code from your previous post on several compilers, with limited success (details below). For another approach to this problem, see my posting "Name hiding and dependent bases" from Nov. 12, archived at http://x37.deja.com/[ST_rn=md]/threadmsg_md.xp?thitnum=59&mhitnum=0&CONT EXT=942698462.658178048&new=0
I'm still hoping for a response to my posting, but at this point the correctness of my code has not been resolved. If it is in fact correct, then it could be used to get much the same effect as your code. In particular, you could write a global template function with some default behavior, then optionally "override" it with a member template providing "specialized" behavior which propagates to derived classes. You could also compare typeof(foo<T>) with typeof(::foo<T>) and use the boolean result to drive partial specializations.
Anyway, I made a few additions to your code so I could compile and test it:
Since only one of the four compilers successfully processed your code and produced the desired answer, this tends to confirm that your interpretation of 5.3.1p3 is correct and that there is no loophole by which a pointer to member may be formed from an unqualified name.
>I tried the code from your previous post on several compilers, with >limited success (details below). For another approach to this problem, >see my posting "Name hiding and dependent bases" from Nov. 12, archived >at >http://x37.deja.com/[ST_rn=md]/threadmsg_md.xp?thitnum=59&mhitnum=0&CONT >EXT=942698462.658178048&new=0
>I'm still hoping for a response to my posting, but at this point the >correctness of my code has not been resolved. If it is in fact >correct, then it could be used to get much the same effect as your >code. In particular, you could write a global template function with >some default behavior, then optionally "override" it with a member >template providing "specialized" behavior which propagates to derived >classes. You could also compare typeof(foo<T>) with typeof(::foo<T>) >and use the boolean result to drive partial specializations.
Your message slipped by me -- glad you pointed it out. I will respond in a moment :).
If this is how Metrowerks responds to the code, then it has big problems that have nothing to do with 5.3.1p3 of the standard. It doesn't seem to be able to recognize that line 23 is a definition of the template's member class "nested" rather than a redefinition of the template itself. This is as bad as, if not worse than, MSVC.
>Since only one of the four compilers successfully processed your code >and produced the desired answer, this tends to confirm that your >interpretation of 5.3.1p3 is correct and that there is no loophole by >which a pointer to member may be formed from an unqualified name.
None of the compilers that failed failed for the right reason so I don't take this as sign about the existence or non-existence of the loopphole.
The error (or at least warning) that I would expect based on 5.3.1p3 would occur here:
template< class _Typ, int memfun > struct memfun_tester<_Typ, memfun >::nested { enum { has_memfun = sizeof(test(memfun)) != sizeof(no) }; ^^^^^^ when the template is instantiated with _Typ equal to a type that has a public or protected member function named memfun. The error should be something like that it cannot convert "memfun" to a pointer to a member function or that it is an invalid use memfun in some way. I have yet to find a compiler that actually gives such an error or warning.
gcc's behavior suggests not that there is no loophole or that my interpretation of 5.3.1p3 is wrong, but that it doesn't conform to 14.6.1p5 which is the paragraph fvali pointed out that got all of this started :).
John Madsen
jmadsen at rci dot rutgers dot edu
[ Send an empty e-mail to c++-h...@netlab.cs.rpi.edu for info ] [ about comp.lang.c++.moderated. First time posters: do this! ]
> Given this, key_type_tester<T>::nested::has_key_type is a compile time > constant which is true it T has an inner type named key_type and false if > it doesn't (well, almost ...).
I'm frightened by the prospect that this code could work. While I like the idea of introspection, the mechanism behind this code would more often manifest itself as a trap. Consider:
template < class A, class B > class DeriveWithBuffer: public A { private: unsigned char buffer[ sizeof(B) ];
Lisa Lippincott <lisa_lippinc...@bigfix.com> wrote: >I'm frightened by the prospect that this code could work. While I like >the idea of introspection, the mechanism behind this code would more >often manifest itself as a trap. Consider:
[ trap snipped ]
Yes, I agree that the "feature" described in 14.6.1p5 can have odd, and except in this introspection case, unwanted effects. I can't figure out why this in the standard unless, although I think this is very unlikely, it was to allow something like what fvali has proposed. I would love to hear from anyone who knows what it's doing there.
John Madsen
jmadsen at rci dot rutgers dot edu
[ Send an empty e-mail to c++-h...@netlab.cs.rpi.edu for info ] [ about comp.lang.c++.moderated. First time posters: do this! ]
> >I tried the code from your previous post on several compilers, with > >limited success (details below). [...] > >Anyway, I made a few additions to your code so I could compile and test > >it:
> If this is how Metrowerks responds to the code, then it has big problems > that have nothing to do with 5.3.1p3 of the standard. It doesn't seem to > be able to recognize that line 23 is a definition of the template's member > class "nested" rather than a redefinition of the template itself. This is > as bad as, if not worse than, MSVC.
Ah, you're right, there doesn't seem to be anything wrong with the code Metrowerks is complaining about. The error message from MSVC was equally pointless:
...\unqualptrtomember.cpp(28) : error C2027: use of undefined type 'nested' ...\unqualptrtomember.cpp(14) : see declaration of 'nested' ...\unqualptrtomember.cpp(28) : error C2065: 'has_memfun' : undeclared identifier ...\unqualptrtomember.cpp(29) : error C2027: use of undefined type 'nested' ...\unqualptrtomember.cpp(14) : see declaration of 'nested'
So, no conclusion can be drawn from the fact that Metrowerks and MS rejected the code.
> >Since only one of the four compilers successfully processed your code > >and produced the desired answer, this tends to confirm that your > >interpretation of 5.3.1p3 is correct and that there is no loophole by > >which a pointer to member may be formed from an unqualified name.
> None of the compilers that failed failed for the right reason so I don't > take this as sign about the existence or non-existence of the loopphole.
> The error (or at least warning) that I would expect based on 5.3.1p3 would > occur here:
> template< class _Typ, int memfun > > struct memfun_tester<_Typ, memfun >::nested { > enum { has_memfun = sizeof(test(memfun)) != sizeof(no) }; > ^^^^^^ > when the template is instantiated with _Typ equal to a type that has a > public or protected member function named memfun. The error should be > something like that it cannot convert "memfun" to a pointer to a member > function or that it is an invalid use memfun in some way.
You're assuming that a 'memfun' defined as a member of some base class of memfun_tester is allowed to hide 'int memfun' the template parameter. It seems to me that the whole question of the validity of fvali's code revolves around whether the Standard actually requires this to happen. Certainly if 'memfun' were defined as a member of memfun_tester itself, then the member memfun would hide the template parameter memfun under 14.6.1p5. A member of a base class is implicitly also a member of derived classes, so fvali argued that 14.6.1p5 applies to base-class members as well. On the other hand, the Standard also contains language, particularly in 14.6.2p5, which prohibits base-class members from intruding into derived template classes in such potentially surprising ways, and it is arguably a defect in the Standard if there is a loophole in that language. I think the question of whether fvali's code is good remains open.
If the base memfun is not allowed to hide the template parameter memfun, then gcc would be correct in accepting the code above, in always assuming that 'memfun' refers to the template parameter, and in producing the output it produces. In other words, the fact that it doesn't report an error does not imply that it is actually taking the address of a member as you want.
> I have yet to > find a compiler that actually gives such an error or warning.
> gcc's behavior suggests not that there is no loophole or that my > interpretation of 5.3.1p3 is wrong, but that it doesn't conform to 14.6.1p5 > which is the paragraph fvali pointed out that got all of this started
> Yes, I agree that the "feature" described in 14.6.1p5 can have odd, and > except in this introspection case, unwanted effects. I can't figure out > why this in the standard unless, although I think this is very unlikely, it > was to allow something like what fvali has proposed. I would love to hear > from anyone who knows what it's doing there.
> John Madsen
Hiya John, I don't really know what that paragraph is doing there but after orignally discussing this problem with Andrei, the more i had thought about it the less I felt that any solution based on 14.6.1p5 is really guaranteed to work. I would have posted my thoughts on this much sooner - but I was unaware there was any further discussion (and there wasn't for the longest time when I actively followed this group) on this topic till I encountered this thread.
Here is my reasoning (which I hope is flawed ;-)) - 14.6.2.1 says - [temp.dep.type] 14.6.2.1 Dependent types 1 A type is dependent if it is — a template parameter, — a qualifiedid with a nestednamespecifier which contains a classname that names a dependent type or whose unqualifiedid names a dependent type, — a cvqualified type where the cvunqualified type is dependent, — a compound type constructed from any dependent type, — an array type constructed from any dependent type or whose size is specified by a constant expression that is valuedependent, — a templateid in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is typedependent or valuedependent.
The only way key_type will be unbound and looked up at the point of the template instantiation (14.6.4.1) in both the context of the template definition and the context of the point of instantiation is if 'key_type' is considered a template parameter, right?
If its lookup is not posponed then it must be bound in the definition context, right? Which means it would have to be an error if not found in the definition context - without any instantiation of templates.
If 'key_type' is considered a template parameter and its lookup is postponed till the point of instantiation, it seems 'screwed up' (sorry for getting all technical) that upon look up at the point of instantiation 'key_type' will not be considered a template parameter and will now be bound to a member of a base class of our instantiated template class.
Also, keep in mind that 14.6.1p5 is in a section that discusses locally declared names within templates.
Therefore : template<class T> struct S { struct A; // locally declares A
};
So A is a local declaration within the class template S and is subject to the rules of 14.6.1 but what about
struct I { struct A; // Note: not declared in a template
};
template<class T> struct S : T { };
In S<I> can 'A' be considered a locally declared name?
Anyway I find this all fairly unclear. What do you folks think of all this?
I hoped some of the C++ gurus would post a definitive answer resolving this issue, or someone would log a defect report - but if this has been done, I have missed it.
regards, -fais
[ Send an empty e-mail to c++-h...@netlab.cs.rpi.edu for info ] [ about comp.lang.c++.moderated. First time posters: do this! ]