Topic: Template specialisation and base classes
Author: "Earl Purple" <earlpurple@gmail.com>
Date: Thu, 9 Feb 2006 15:13:15 CST Raw View
cnjinhao@hotmail.com wrote:
> the following code could works well
>
> #include <iostream>
>
> class Foo;
>
> char __fun(Foo*);
> long __fun(...);
>
> template<typename _T>
> struct is_Foo
> {
> enum{value = sizeof(char) == sizeof(__fun((_T*)0)) };
> };
>
> class Foo
> {
> public:
> virtual ~Foo() {}
>
> };
>
>
> template< typename T >
> int value( const T* pT)
> {
> if(is_Foo<T>::value) //this statement will be optimized
> return 1;
> else
> return 0;
> }
>
> class Bar : public Foo{};
>
> int main()
> {
> Bar bar;
> Foo* foo = &bar;
> int *p;
> std::cout << value( foo ) << ' ' << value( &bar ) << ' ' <<
> value(p)<<'\n';
>
> }
>
That's close to one of the solutions in Herb Sutter's GOTW 071 (
http://www.gotw.ca/gotw/071.htm ), but even he seems to think this is
not ideal.
Plus it is non-extensible.
---
[ 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: v.Abazarov@comAcast.net ("Victor Bazarov")
Date: Fri, 20 Jan 2006 04:32:48 GMT Raw View
Earl Purple wrote:
> Victor Bazarov wrote:
>
>> Conversions are not (and cannot be) applied when deducing template
>> arguments. Since 'value' is not specialised on 'Bar', when you call
>> 'value(&bar)', the actual type of 'bar' is used to deduce the
>> template argument.
>>
>> Why? In some contexts the compiler cannot know what the "actual"
>> type of the object is, if your object is of polymorphic type.
>> That's why the _static_ type has to be used. The static type of
>> 'foo' is 'Foo*', and the static type of '&bar' is 'Bar*'.
>>
>> V
>
> But it would resolve it if there were no template.
So? If you provide a _single_ function declared
int value(const Foo*);
it will be viable because the conversion it needs does in fact exist.
Now, if you provide two functions, one
int value(const Foo*);
and the other
int value(const Bar*);
(simple overloading), which one is going to be picked for the call
value(&bar)
?
> I'm not expecting
> it to know that the Foo* is really a Bar* - if I wanted to do that I
> could of course do it easily by getting my specialisation to call a
> virtual function in Foo (thus invoking the Bar one when I have a Bar).
>
> It just looks to me like Foo* should be a closer (more specific)
> match.
Wait, wait... You think that
template<> int value<Foo>(const Foo*); // explicit specialisation
is a _closer_ (more specific) match than
template<> int value<Bar>(const Bar*); // implicit specialisation
if the argument is in fact a pointer to Bar? How is that possible?
The explicit specialisation of 'value' on Foo would require a derived-
to-base pointer conversion. A new template generated with 'T == Bar'
needs no such conversion, the fact that there exist specialisations
of the template notwithstanding.
> (Note I have posted this to comp.std.c++ specifically because I know
> it's not standard, but think it should be).
Why should it be? Please present your argument.
V
---
[ 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: "Earl Purple" <earlpurple@gmail.com>
Date: Fri, 20 Jan 2006 12:15:19 CST Raw View
> So? If you provide a _single_ function declared
>
> int value(const Foo*);
>
> it will be viable because the conversion it needs does in fact exist.
> Now, if you provide two functions, one
>
> int value(const Foo*);
>
> and the other
>
> int value(const Bar*);
>
> (simple overloading), which one is going to be picked for the call
>
> value(&bar)
>
The second one obviously. But that's not the point I'm making. The
point is that if I had
int value( const Foo * );
int value ( const Blob * );
where Blob is unrelated to Foo or Bar then the compiler would correctly
resolve that value( &bar) is legal because it matches one
specification. True that if I had
int value( const void * );
the compiler may get upset because it matches two and can't resolve it
but templates are different and they are supposed to resolve to the
closest match.
> Wait, wait... You think that
>
> template<> int value<Foo>(const Foo*); // explicit specialisation
>
> is a _closer_ (more specific) match than
>
> template<> int value<Bar>(const Bar*); // implicit specialisation
>
> if the argument is in fact a pointer to Bar? How is that possible?
Not sure exactly what you mean by the "implicit specialisation". There
doesn't seem to be any specialisation for Bar (the compiler assigns it
to the general template).
> > (Note I have posted this to comp.std.c++ specifically because I know
> > it's not standard, but think it should be).
>
> Why should it be? Please present your argument.
>
> V
because it would be very useful to provide traits-type functions and
classes where there is a default case.
Apart from the fact that I would think most programmers would expect
the behaviour I have described (because it does with no default case).
---
[ 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: Victor Bazarov <v.Abazarov@comAcast.net>
Date: Fri, 20 Jan 2006 13:59:08 CST Raw View
Earl Purple wrote:
>>So? If you provide a _single_ function declared
>>
>> int value(const Foo*);
>>
>>it will be viable because the conversion it needs does in fact exist.
>>Now, if you provide two functions, one
>>
>> int value(const Foo*);
>>
>>and the other
>>
>> int value(const Bar*);
>>
>>(simple overloading), which one is going to be picked for the call
>>
>> value(&bar)
>>
>
>
> The second one obviously. But that's not the point I'm making. The
> point is that if I had
>
> int value( const Foo * );
> int value ( const Blob * );
>
> where Blob is unrelated to Foo or Bar then the compiler would correctly
> resolve that value( &bar) is legal because it matches one
> specification.
Here you have two functions in a set. When you have a function template,
you essentially have the _unbounded_ set of overloaded functions. Among
them exists
int value(const Bar *);
which is created if 'T' is deduced to be 'Bar'. That's what you have in
your example. That's why I asked to choose between those two (unlike the
two in your example, with 'Blob').
> True that if I had
>
> int value( const void * );
>
> the compiler may get upset because it matches two and can't resolve it
> but templates are different and they are supposed to resolve to the
> closest match.
That's irrelevant. Your template doesn't have 'void', it has 'T'.
>>Wait, wait... You think that
>>
>> template<> int value<Foo>(const Foo*); // explicit specialisation
>>
>>is a _closer_ (more specific) match than
>>
>> template<> int value<Bar>(const Bar*); // implicit specialisation
>>
>>if the argument is in fact a pointer to Bar? How is that possible?
>
>
> Not sure exactly what you mean by the "implicit specialisation". There
> doesn't seem to be any specialisation for Bar (the compiler assigns it
> to the general template).
The compiler _generates_ one. The implicitly instantiated template with
the 'T' == 'Bar' is the implicit specialisation. The existence of any
other specialisation makes no difference. Since there is nothing to stop
the compiler, the implicit is better than explicit. That's the rule.
>>>(Note I have posted this to comp.std.c++ specifically because I know
>>>it's not standard, but think it should be).
>>
>>Why should it be? Please present your argument.
>>
>>V
>
>
> because it would be very useful to provide traits-type functions and
> classes where there is a default case.
I don't follow. So, provide traits-type function for Bar as well, and it
is going to be chosen over the generic template definition. The template
argument type deduction produces the _exact_ match. If you want to change
that, you need to (a) provide a compelling argument describing what
problem it solves that cannot be solved by existing features of the
language and (b) convince everybody that your change doesn't break any of
the existing code or that such breakage is a small price to pay for all
the benefits your solution brings. Here I see neither, sorry.
> Apart from the fact that I would think most programmers would expect
> the behaviour I have described (because it does with no default case).
Why would they? I mean, where did _you_ get the idea? Learning the
language is infinitely better source of information than guessing what
it should contain, don't you think? So, anybody who bothered to _learn_
how the compiler handles the situation would not _expect_ the behaviour
you described.
V
---
[ 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: Bart van Ingen Schenau <bart@ingen.ddns.info>
Date: Sat, 21 Jan 2006 01:38:20 CST Raw View
Earl Purple wrote:
> Vicor Bazarov wrote:
>
>> Wait, wait... You think that
>>
>> template<> int value<Foo>(const Foo*); // explicit specialisation
>>
>> is a _closer_ (more specific) match than
>>
>> template<> int value<Bar>(const Bar*); // implicit specialisation
>>
>> if the argument is in fact a pointer to Bar? How is that possible?
>
> Not sure exactly what you mean by the "implicit specialisation". There
> doesn't seem to be any specialisation for Bar (the compiler assigns it
> to the general template).
The implicit specialisation is what you get when the compiler has
finished the process of template argument deduction for the expression
value(&bar)
You seem to be misunderstanding the way that template specialisation and
template argument deduction work.
During template argument deduction, the compiler tries to determine
which template arguments to use, such that a best-matching template
instantiation can be created for the function call. In this process,
the compiler looks exclusively to the actual arguments of the call and
the template parameters that were not explicitly specified.
Once the template arguments of the best matching instantiation are
determined, the compiler will look if there is an explicit
specialisation for this set of template arguments. If there is not, the
compiler will create an implicit instantiation of the template, based
on the base template.
Template specialisations come only around in the second step, when the
compiler needs to generate the code to call a particular function.
Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://www.eskimo.com/~scs/C-faq/top.html
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/
---
[ 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: "Earl Purple" <earlpurple@gmail.com>
Date: Mon, 23 Jan 2006 17:42:58 CST Raw View
Victor Bazarov wrote:
>
> I don't follow. So, provide traits-type function for Bar as well, and it
> is going to be chosen over the generic template definition. The template
> argument type deduction produces the _exact_ match. If you want to change
> that, you need to (a) provide a compelling argument describing what
> problem it solves that cannot be solved by existing features of the
> language and (b) convince everybody that your change doesn't break any of
> the existing code or that such breakage is a small price to pay for all
> the benefits your solution brings. Here I see neither, sorry.
Are the language rules to make life easy for the programmer or for the
compiler writers? I know that export templates were considered too hard
for compiler writers which is why the rules are as they are with
templates.
But anyway, here is an example of where you might want it. Now suppose
we want a smart pointer that clones (maybe implements copy-on-write).
The difficulty here is how to make the copy without making our
smart-pointer intrusive (forcing the user to inherit from a clone type)
and also to allow 3rd-party types where the normal copy method is a
general copy-constructor.
Now although we are allowed co-variant return types, it would be
impossible to implement here so we'll create a generic function:
template < typename T >
do_clone( T* src, T* & dest )
{
dest = new T( *src );
}
Now suppose I have a type that uses a member clone() function. Then our
routine would be:
template < >
do_clone( clone_base * src, clone_base * & dest )
{
dest = src->clone();
}
and the smart-pointer can be implemented with do_clone<T> and work
automatically even with derived-classes from do_clone. (But at the same
time not enforce the user to derive from clone_base and classes that
use another method that isn't clone() will be able to specialise
do_clone externally for that particular group).
---
[ 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: "Victor Bazarov" <v.Abazarov@comAcast.net>
Date: Tue, 24 Jan 2006 01:16:05 CST Raw View
Earl Purple wrote:
> Victor Bazarov wrote:
>>
>> I don't follow. So, provide traits-type function for Bar as well,
>> and it is going to be chosen over the generic template definition.
>> The template argument type deduction produces the _exact_ match. If
>> you want to change that, you need to (a) provide a compelling
>> argument describing what problem it solves that cannot be solved by
>> existing features of the language and (b) convince everybody that
>> your change doesn't break any of the existing code or that such
>> breakage is a small price to pay for all the benefits your solution
>> brings. Here I see neither, sorry.
>
> Are the language rules to make life easy for the programmer or for the
> compiler writers?
Both. The language is full of trade-offs.
> I know that export templates were considered too
> hard for compiler writers which is why the rules are as they are with
> templates.
Not only 'export' is the problem. There are other places (like name
lookup) where rules for templates are different from rules for regular
functions and classes, IIRC.
> But anyway, here is an example of where you might want it. Now suppose
> we want a smart pointer that clones (maybe implements copy-on-write).
> The difficulty here is how to make the copy without making our
> smart-pointer intrusive (forcing the user to inherit from a clone
> type) and also to allow 3rd-party types where the normal copy method
> is a general copy-constructor.
>
> Now although we are allowed co-variant return types, it would be
> impossible to implement here so we'll create a generic function:
>
> template < typename T >
> do_clone( T* src, T* & dest )
I guess you mean
void do_clone( T* src, T* & dest )
> {
> dest = new T( *src );
> }
>
> Now suppose I have a type that uses a member clone() function. Then
> our routine would be:
>
> template < >
> do_clone( clone_base * src, clone_base * & dest )
void do_clone( ...
> {
> dest = src->clone();
> }
>
> and the smart-pointer can be implemented with do_clone<T> and work
> automatically even with derived-classes from do_clone. (But at the
> same time not enforce the user to derive from clone_base and classes
> that
> use another method that isn't clone() will be able to specialise
> do_clone externally for that particular group).
Now, it seems that you're trying to solve a particular problem, and
for that you're looking to change the language. Doesn't seem fair
to all who (a) have other problems to solve using existing mechanisms
and (b) who don't really care for your particular problem. No?
To solve what you have you can rely on SFINAE. If the class T does
not have 'clone' member, you just fall back onto another definition:
template<class T> void do_clone(T* s, T*& d ...)
{
d = new T(*s);
}
struct clone_base
{
virtual clone_base* clone() const = 0;
};
struct clone_too : clone_base
{
clone_base* clone() const { return new clone_too(*this); }
};
void do_clone(clone_base* s, clone_base *& d,
clone_base* (clone_base::*)() const = &clone_base::clone)
{
d = s->clone();
}
struct no_clone {
};
int main()
{
clone_base* p = new clone_too();
clone_base* pp;
do_clone(p, pp);
no_clone *np = new no_clone(), *npp;
do_clone(np, npp);
}
In this particular situation, 'clone_base' has 'clone' member of
a specific signature. I may have screwed up somewhere, but it seems
what you're looking for. If not, explain further. I recomend,
however, to move to 'comp.lang.c++' where you'd inquire how to solve
your problem using existing C++ means instead of suggesting to change
the language.
V
---
[ 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: "Earl Purple" <earlpurple@gmail.com>
Date: Tue, 24 Jan 2006 12:15:14 CST Raw View
Victor Bazarov wrote:
> Now, it seems that you're trying to solve a particular problem, and
> for that you're looking to change the language. Doesn't seem fair
> to all who (a) have other problems to solve using existing mechanisms
> and (b) who don't really care for your particular problem. No?
It was an example of a case where such a change would be useful. It is
not necessarily the only example where it might be useful.
> To solve what you have you can rely on SFINAE.
A technique I could presumably google for. What exactly is SFINAE
though? I guess some kind of adapter as you go on to...
If the class T does
> not have 'clone' member, you just fall back onto another definition:
>
> template<class T> void do_clone(T* s, T*& d ...)
> {
> d = new T(*s);
> }
>
> struct clone_base
> {
> virtual clone_base* clone() const = 0;
> };
> struct clone_too : clone_base
> {
> clone_base* clone() const { return new clone_too(*this); }
> };
>
> void do_clone(clone_base* s, clone_base *& d,
> clone_base* (clone_base::*)() const = &clone_base::clone)
> {
> d = s->clone();
> }
>
> struct no_clone {
> };
> int main()
> {
> clone_base* p = new clone_too();
> clone_base* pp;
> do_clone(p, pp);
> no_clone *np = new no_clone(), *npp;
> do_clone(np, npp);
> }
>
> In this particular situation, 'clone_base' has 'clone' member of
> a specific signature. I may have screwed up somewhere, but it seems
> what you're looking for. If not, explain further.
Andi f you screw up what chance does it give the rest of us (and all
those out there who are far lower in skill level even than me). The
idea of the change is to make life easier for the general programmer.
By the way, it is supposed to work for where you have a general type T
(in at template) that you want to clone and don't know how to clone it.
So you create a traits-type do_clone() function which can be
specialised. T could be std::string or it could be Bar derived from
clone_base or it could be Blob derived from a 3rd party base-class that
uses Clone() with a capital C.
---
[ 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: cnjinhao@hotmail.com
Date: Tue, 24 Jan 2006 12:15:55 CST Raw View
the following code could works well
#include <iostream>
class Foo;
char __fun(Foo*);
long __fun(...);
template<typename _T>
struct is_Foo
{
enum{value = sizeof(char) == sizeof(__fun((_T*)0)) };
};
class Foo
{
public:
virtual ~Foo() {}
};
template< typename T >
int value( const T* pT)
{
if(is_Foo<T>::value) //this statement will be optimized
return 1;
else
return 0;
}
class Bar : public Foo{};
int main()
{
Bar bar;
Foo* foo = &bar;
int *p;
std::cout << value( foo ) << ' ' << value( &bar ) << ' ' <<
value(p)<<'\n';
}
---
[ 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: "Earl Purple" <earlpurple@gmail.com>
Date: Thu, 19 Jan 2006 12:44:57 CST Raw View
The following code:
#include <iostream>
template< typename T >
int value( const T* pT )
{
return 0;
}
class Foo
{
public:
virtual ~Foo() {}
};
template <>
int value( const Foo * pFoo )
{
return 1;
}
class Bar : public Foo
{
};
int main()
{
Bar bar;
Foo* foo = &bar;
std::cout << value( foo ) << ' ' << value( &bar ) << '\n';
}
It will output 1 0. Why does it not specialise the second call? Is this
desired by the standard? Why? (It would be very useful to be able to
define certain "traits" using overloads in this manner. It works of
course if there is no generic template but simply different overloads
of value() ).
---
[ 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: v.Abazarov@comAcast.net (Victor Bazarov)
Date: Thu, 19 Jan 2006 21:12:04 GMT Raw View
Earl Purple wrote:
> The following code:
>
> #include <iostream>
>
> template< typename T >
> int value( const T* pT )
> {
> return 0;
> }
>
> class Foo
> {
> public:
> virtual ~Foo() {}
> };
>
> template <>
> int value( const Foo * pFoo )
> {
> return 1;
> }
>
> class Bar : public Foo
> {
> };
>
> int main()
> {
> Bar bar;
> Foo* foo = &bar;
>
> std::cout << value( foo ) << ' ' << value( &bar ) << '\n';
> }
>
> It will output 1 0. Why does it not specialise the second call? Is this
> desired by the standard? Why? (It would be very useful to be able to
> define certain "traits" using overloads in this manner. It works of
> course if there is no generic template but simply different overloads
> of value() ).
Conversions are not (and cannot be) applied when deducing template
arguments. Since 'value' is not specialised on 'Bar', when you call
'value(&bar)', the actual type of 'bar' is used to deduce the template
argument.
Why? In some contexts the compiler cannot know what the "actual" type
of the object is, if your object is of polymorphic type. That's why
the _static_ type has to be used. The static type of 'foo' is 'Foo*',
and the static type of '&bar' is 'Bar*'.
V
---
[ 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: "Earl Purple" <earlpurple@gmail.com>
Date: Thu, 19 Jan 2006 18:09:05 CST Raw View
Victor Bazarov wrote:
> Conversions are not (and cannot be) applied when deducing template
> arguments. Since 'value' is not specialised on 'Bar', when you call
> 'value(&bar)', the actual type of 'bar' is used to deduce the template
> argument.
>
> Why? In some contexts the compiler cannot know what the "actual" type
> of the object is, if your object is of polymorphic type. That's why
> the _static_ type has to be used. The static type of 'foo' is 'Foo*',
> and the static type of '&bar' is 'Bar*'.
>
> V
But it would resolve it if there were no template. I'm not expecting it
to know that the Foo* is really a Bar* - if I wanted to do that I could
of course do it easily by getting my specialisation to call a virtual
function in Foo (thus invoking the Bar one when I have a Bar).
It just looks to me like Foo* should be a closer (more specific) match.
(Note I have posted this to comp.std.c++ specifically because I know
it's not standard, but think it should be).
---
[ 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 ]