Topic: function template specialization deduction


Author: nesotto@cs.auc.dk ("THORSTEN OTTOSEN")
Date: Wed, 8 Jan 2003 21:44:01 +0000 (UTC)
Raw View
"Graeme Prentice" <gp1@paradise.net.nz> wrote in message
news:5k3p1vccinj8l0u196lo4kfb3q0r1numv7@4ax.com...
> On Wed, 8 Jan 2003 18:02:24 +0000 (UTC), nesotto@cs.auc.dk ("THORSTEN
> OTTOSEN") wrote:
> >I just realized why it would not work that good: it would requre that
users
> >include their headers in
> >a very strict ordering, eg., the stack header would have to be included
> >before
> >my header.
>
> But you could include <stack> yourself in your header
>
> // abc.h
>
> #include <stack>
> template< typename A >
> inline void insert( std::stack<A>& c,  A const& v )
> {
>     c.push( v );
> }

oh..yes..I should have been clearer.The whole point is to _not_ include the
header. Using the
typedef trick we can write generic code with template function
specializations without have seen the definition
of the classes; the potential compile-time speed-up could be huge; moreover,
the pretty big precompiled headers
could be much smaller.

regards

Thorsten


---
[ 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: gp1@paradise.net.nz (Graeme Prentice)
Date: Thu, 9 Jan 2003 18:40:53 +0000 (UTC)
Raw View
On Wed, 8 Jan 2003 15:16:50 +0000 (UTC), rani_sharoni@hotmail.com ("Rani
Sharoni") wrote:

>
>The following code complied fine using both Comeau C/C++ 4.3.0.1 and GCC 3.2
>and I wonder why:
>template<typename T>
>struct A {
>    typedef A self_type;
>    A(const T& = T()) {}
>};
>
>template<typename T>
>char *f(const A<T>&, const A<T>&) { return 0; }
>
>template<typename T>
>long *f(const A<T>&, const typename A<T>::self_type&) { return 0; }
>
>A<int> a;
>
>char *x1 = f(a, a); // what makes the first f better?
>long *x2 = f(a, 1); // conversion allowed !
>
>I see no order between the above template functions and both seem to have
>exact match for f(a,a).
>Any clarifications for this issue?

I think so.

Regarding the conversion with second of the two calls to f(), the
conversion is allowed because the second parameter is a non-deduced
context
const typename A<T>::self_type&
- a nested name specifier is a non-deduced context so the parameter
doesn't participate in deduction, and conversions are allowed on
parameters that don't participate in deduction.

This is also probably why partial ordering favours the first function
over the second  - when it makes a "call" to the second function with
the arguments from the first function, the second parameter doesn't
participate because it's a non deduced context in the second function,
and deduction succeeds.

When you do it the other way round, the second parameter in the first
function f() is a deduced context so it participates and deduction fails
because the compiler doesn't know how to generate a specialization of
A<T> for a synthesized type T   -   why doesn't the compiler know how to
do this ?????   -  I'm not sure, but it's probably because generating a
specialization of A<T> for a synthesized type T is in general, not
possible.

e.g. what does the default parameter T() of the constructor A(const T& =
T())  mean for a synthesized type T.  How can it produce a full
specialisation of the class A<T> so as to do proper lookup on
A<T>::self_type  - the compiler probably doesn't even try.


Thinking about this made me wonder  - what happens if the typedef in A
specifies a "member of T"  - as I do in the code below.  Now when the
compiler comes to do the partial ordering, it has no possible way to
determine what type "atype" has.  For the code below, Comeau and GCC
give an ambiguity.  If I remove the third parameter from f (the T*),
the ambiguity goes away and the  "char*" version of f() gets called.

Both functions are callable  - if you remove the first function
altogether, it calls the long* version of f() just fine.  The
non-deduced context A<T>::atype allows the deduction to succeed one way
but not the other.


I also wonder when doing all this partial ordering ordering and overload
resolution the compiler adds a lot of temporary names to the symbol
tables as it proceeds to generate specializations for an arbitrary
number of templates which it then has to remove from the symbol tables
- how does it do that I wonder  - it sounds either complex or time
consuming.


#include <iostream>

template<typename T>
struct A {
    typedef typename T::abc atype;
    //A(const T& = T()) {}
};

struct c1 { typedef A<c1> abc; };

template<typename T>
void f( const A<T>&, const A<T>&, T*)
{
    std::cout << "\nchar*";
}

template<typename T>
char *f( const A<T>&, const typename A<T>::atype &, c1*)
{
    std::cout << "\nlong*";
}

A<c1> a;
c1 oc1;

int main()
{
    f<c1>( a, a, &oc1);
}


Graeme

---
[ 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: tom_usenet@hotmail.com (tom_usenet)
Date: Wed, 8 Jan 2003 00:29:46 +0000 (UTC)
Raw View
On Tue, 7 Jan 2003 18:55:11 +0000 (UTC), nesotto@cs.auc.dk ("THORSTEN
OTTOSEN") wrote:

>Hi,

Hello.

[SNIP]

>My problem is then simply that cannot explain why
>the latter version is a better
>match than the first. I have tried looking at paragraph 13.3.3, 14.5.52 and
>14.8.3 but could not find anything
>that says that
>
>template< typename C, typename V >
> inline void insert( C& c, const V& );
>
>is a worse match than
>
> template< typename A >
> inline void
> insert( A& c, const typename A::container_type::value_type& v );
>
>Is there some rule that "fewer template parameters is better" ?

Not exactly, but the partial function ordering rules in 14.5.5.2 pick
the second in favour of the first.

Basically, the first can match every argument type that the second can
match using template argument deduction for each argument, but the
second cannot match all those of the first (specifically when V isn't
the same as C::container_type::value_type).

Divining that rule-of-thumb from the standardese in 14.5.5.2 requires
a bit of work;  I haven't really done that work but the rule seems
intuitive enough.

Tom

---
[ 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: gp1@paradise.net.nz (Graeme Prentice)
Date: Wed, 8 Jan 2003 00:31:38 +0000 (UTC)
Raw View
On Tue, 7 Jan 2003 18:55:11 +0000 (UTC), nesotto@cs.auc.dk ("THORSTEN
OTTOSEN") wrote:

>Hi,
>
>I was writing some code when I discovered that Comeau could destinguish this
>code (whereas GCC 3.2 and vc7 cannot):
>
> template< typename C, typename V >
> inline void insert( C& c, const V& v )
> {
>     c.insert( c.end(), v );
> }
>
> template< typename A >
> inline void
> insert( A& c, const typename A::container_type::value_type& v )
> {
>     c.push( v );
> }
>
>It works with Comeau 4.3.0.1 and it is extreamly useful because it allows me
>to leave out the inclusion of headers
>like <stack> and beause it gives some kind of fake partial specialization of
>function templates even though it
>doesn't exist. (I guess Comeau could be wrong ).


In my experience, it's very rare for Comeau to be wrong  -  I've found a
grand total of one bug in it.

Did you know that it is illegal to add declarations and definitions to
namespace std. (17.4.3.1) except for certain template specializations.
I guess you're adding these functions to the global namespace so that
would appear to be legal.


>Anyway, consider this usage
>scenario:
>
>// file1.cpp
>#include <vector>
>...
>vector<int> v;
>insert( v, 4 );  // calls v.insert()
>
>// file2.cpp
>#include <stack>
>...
>stack<int> s;
>insert( s, 6 ); // calls s.push()
>
>pretty neat if I may say so; the latter version of insert() will only be
>instantiated with classes like stack which defines
>a container_type tydedef.


I hate to disagree with you, but relying on the presence of a particular
typedef ( container_type ) doesn't seem like a great idea because
there's no guarantee that vector won't have a typedef for container_type
in some implementations or that it won't be added in future.


>My problem is then simply that cannot explain why
>the latter version is a better
>match than the first. I have tried looking at paragraph 13.3.3, 14.5.52 and
>14.8.3 but could not find anything
>that says that
>
>template< typename C, typename V >
> inline void insert( C& c, const V& );
>
>is a worse match than
>
> template< typename A >
> inline void
> insert( A& c, const typename A::container_type::value_type& v );
>
>Is there some rule that "fewer template parameters is better" ?


No, there isnt a rule that says that.

The deduction process synthesizes a "unique type", "unique value" or
"unique class template" and "calls" the other function

When you synthesize a unique type for "A"  (lets call it A) and
substitute, you get
    insert( A& c, const typename A::container_type::value_type& v );

if you "call" the other function with these arguments, V will be deduced
as A::container_type::value_type and the parameter types in the called
function match the calling argument types exactly.

When you do it the other way around, the calling types are
 insert( C&, const V&)
the compiler now has to try to deduce the A in
 const A::container_type::value
from the passed argument V  -  this fails because
A::container_type::value does not have one of the forms listed in
14.8.2.4 para 9

Clearly, there's no possible way a compiler could work out the type of A
for some arbitrary type V  - this is mission impossible, so deduction
fails hence the second function is more specialised than the first.

The call to insert here
>stack<int> s;
>insert( s, 6 ); // calls s.push()

calls your push version of insert because the type of "A" is deduced
from the first argument.

Maybe you could define the stack version as

template< typename A >
inline void insert( std::stack<A>& c,  A const& v )
{
    c.push( v );
}

and only include it when you #include <stack> somehow, rather than
depend on container_type not being present in other containers.  Are you
sure you need a common name for this function for the two container
types?


Graeme

---
[ 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: gp1@paradise.net.nz (Graeme Prentice)
Date: Wed, 8 Jan 2003 04:27:06 +0000 (UTC)
Raw View
On Tue, 7 Jan 2003 18:55:11 +0000 (UTC), nesotto@cs.auc.dk ("THORSTEN
OTTOSEN") wrote:

[snip]

I think I'll have a third go at answering this since my first two
attempts were slightly wrong.

> My problem is then simply that cannot explain why
>the latter version is a better
>match than the first. I have tried looking at paragraph 13.3.3, 14.5.52 and
>14.8.3 but could not find anything
>that says that
>
>template< typename C, typename V >
> inline void insert( C& c, const V& );
>
>is a worse match than
>
> template< typename A >
> inline void
> insert( A& c, const typename A::container_type::value_type& v );
>
>Is there some rule that "fewer template parameters is better" ?


When the deduction process tries to call the second function with
arguments made up from the first, it has argument types of
C& and const V&   -  C and V here are "made up"/synthesized types that
are unique  -  the std doesn't define precisely what is meant by unique
but the most obvious interpretation is that C and V are types that
appear nowhere else in the template parameter list or function parameter
list of either function.

Having made up the types C and V, the compiler tries to "call" the other
function  - for the first parameter, it can deduce the template
parameter A in the called function to be C  - for the second parameter
it can "plug in" C where A is and get
const typename C::container_type::value_type &

from this, it is unable to determine the actual type of value_type to do
a comparison with V  - so deduction fails because it cannot determine
that value_type is the same type as V  -  but even if it could work out
the actual type of value_type,  it is guaranteed that it is not the same
type as V because V is a unique type that doesn't appear anywhere else.


Section 14.5.4.2 gives this example of "uniqueness" applying to non-type
parameters

template<int I, int J>
void f(X<I, J, int>);    // #A

template<int I>
void f(X<I, I, int>);    // #B

14.5.4.2 says B is more specialized than A because when you make up
unique values for I and J in the first function, deduction into B fails
because of the template parameter I being deduced as two different
values.

Graeme

---
[ 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: gp1@paradise.net.nz (Graeme Prentice)
Date: Wed, 8 Jan 2003 05:55:25 +0000 (UTC)
Raw View
On Tue, 7 Jan 2003 18:55:11 +0000 (UTC), nesotto@cs.auc.dk ("THORSTEN
OTTOSEN") wrote:


This is my second of two posts in this thread  - I'm not sure which will
turn up first but I think I got it wrong in my other post.


<<<<<<<<  copied from my other post >>>>>>>>>>>>>>>

>My problem is then simply that cannot explain why
>the latter version is a better
>match than the first. I have tried looking at paragraph 13.3.3, 14.5.52 and
>14.8.3 but could not find anything
>that says that
>
>template< typename C, typename V >
> inline void insert( C& c, const V& );
>
>is a worse match than
>
> template< typename A >
> inline void
> insert( A& c, const typename A::container_type::value_type& v );
>
>Is there some rule that "fewer template parameters is better" ?


No, there isnt a rule that says that.

The deduction process synthesizes a "unique type", "unique value" or
"unique class template" and "calls" the other function

When you synthesize a unique type for "A"  (lets call it A) and
substitute, you get
    insert( A& c, const typename A::container_type::value_type& v );

if you "call" the other function with these arguments, V will be deduced
as A::container_type::value_type and the parameter types in the called
function match the calling argument types exactly.

When you do it the other way around, the calling types are
 insert( C&, const V&)
the compiler now has to try to deduce the A in
 const A::container_type::value
from the passed argument V  -  this fails because
A::container_type::value does not have one of the forms listed in
14.8.2.4 para 9

Clearly, there's no possible way a compiler could work out the type of A
for some arbitrary type V  - this is mission impossible, so deduction
fails hence the second function is more specialised than the first.

<<<<<<<<<<<< end of copy from my other post >>>>>>>>>>>>>>>


I have woken up to the fact that the call  insert( C&, const V&) can
deduce the type of A from the first parameter  - so the fact that it
can't deduce the type of A from the second parameter doesn't matter  -
most likely deduction fails because an arbitrary type C does not have a
container_type member, or because V is not *exactly-the-same* type as
C::container_type::value_type

This looks like a complicated case and relying on the partial ordering
rules to do what you hope doesn't seem like a good idea, even if you can
figure out exactly why it does what it does.

Graeme

---
[ 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: rani_sharoni@hotmail.com ("Rani Sharoni")
Date: Wed, 8 Jan 2003 15:16:50 +0000 (UTC)
Raw View
"tom_usenet" <tom_usenet@hotmail.com> wrote in message
news:3e1b2558.465768343@news.easynet.co.uk...
> > [...]
> >template< typename C, typename V >
> > inline void insert( C& c, const V& );
> >
> >is a worse match than
> >
> > template< typename A >
> > inline void
> > insert( A& c, const typename A::container_type::value_type& v );
> >
> >Is there some rule that "fewer template parameters is better" ?
>
> Not exactly, but the partial function ordering rules in 14.5.5.2 pick
> the second in favour of the first.
I agree.

The following code complied fine using both Comeau C/C++ 4.3.0.1 and GCC 3.2
and I wonder why:
template<typename T>
struct A {
    typedef A self_type;
    A(const T& = T()) {}
};

template<typename T>
char *f(const A<T>&, const A<T>&) { return 0; }

template<typename T>
long *f(const A<T>&, const typename A<T>::self_type&) { return 0; }

A<int> a;

char *x1 = f(a, a); // what makes the first f better?
long *x2 = f(a, 1); // conversion allowed !

I see no order between the above template functions and both seem to have
exact match for f(a,a).
Any clarifications for this issue?

Thanks,
Rani


---
[ 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: nesotto@cs.auc.dk ("THORSTEN OTTOSEN")
Date: Wed, 8 Jan 2003 18:02:24 +0000 (UTC)
Raw View
"Graeme Prentice" <gp1@paradise.net.nz> wrote in message
news:g2dm1vs0itq0ur51qi1m9lbjumoda8rd7b@4ax.com...

> Maybe you could define the stack version as
>
> template< typename A >
> inline void insert( std::stack<A>& c,  A const& v )
> {
>     c.push( v );
> }
>
> and only include it when you #include <stack> somehow, rather than
> depend on container_type not being present in other containers.

I just realized why it would not work that good: it would requre that users
include their headers in
a very strict ordering, eg., the stack header would have to be included
before
my header.

regards

Thorsten


---
[ 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: nesotto@cs.auc.dk ("THORSTEN OTTOSEN")
Date: Wed, 8 Jan 2003 20:33:42 +0000 (UTC)
Raw View
"Graeme Prentice" <gp1@paradise.net.nz> wrote in message
news:g2dm1vs0itq0ur51qi1m9lbjumoda8rd7b@4ax.com...
[snip]
> I hate to disagree with you, but relying on the presence of a particular
> typedef ( container_type ) doesn't seem like a great idea because
> there's no guarantee that vector won't have a typedef for container_type
> in some implementations or that it won't be added in future.

at least we will get a compile time error since vector doesn't have push().
Anyway, I don't plan on using it for production code since too few compilers
support it. I just thought the idea was really cool, I mean, no header
inclusion could
probably improve compilation time a lot.

Maybe the technique could be used to make a builtin tagging mecahnism for
all
the standard classes? The standard should then give each class a unique
typedef, eg.

template< typename T, typename T2 >
class pair
{
public:
    class tag {};
    typedef tag pair_tag;
...
}

template< ... >
class vector
{
public:
 class tag { };
 typedef tag vector_tag;
};

Then we could identify classes without including they header:

template< typename C > // C = some container
void some_algorithm( const C& c )
{
     some_algorithm( c, C::tag() );
}

template< typename C, typename Tag >
void some_algorithm( const C& c, Tag t ); // default version

template< typename C >
void some_algorithm( const C& c, typename C::vector_tag ); // specialized
for vector with including header

I haven't thought this complete through, just wanted to mention it .-) Maybe
it won't work (or _most_ likely ... )


> Maybe you could define the stack version as
>
> template< typename A >
> inline void insert( std::stack<A>& c,  A const& v )
> {
>     c.push( v );
> }
>
> and only include it when you #include <stack> somehow, rather than
> depend on container_type not being present in other containers.

I thought about this; at first it seemed an impossible task to do
portable, but then again boost could maybe come up with something.


>Are you
> sure you need a common name for this function for the two container
> types?

yes.

regards

Thorsten


---
[ 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: gp1@paradise.net.nz (Graeme Prentice)
Date: Wed, 8 Jan 2003 20:55:23 +0000 (UTC)
Raw View
On Wed, 8 Jan 2003 18:02:24 +0000 (UTC), nesotto@cs.auc.dk ("THORSTEN
OTTOSEN") wrote:

>"Graeme Prentice" <gp1@paradise.net.nz> wrote in message
>news:g2dm1vs0itq0ur51qi1m9lbjumoda8rd7b@4ax.com...
>
>> Maybe you could define the stack version as
>>
>> template< typename A >
>> inline void insert( std::stack<A>& c,  A const& v )
>> {
>>     c.push( v );
>> }
>>
>> and only include it when you #include <stack> somehow, rather than
>> depend on container_type not being present in other containers.
>
>I just realized why it would not work that good: it would requre that users
>include their headers in
>a very strict ordering, eg., the stack header would have to be included
>before
>my header.

But you could include <stack> yourself in your header

// abc.h

#include <stack>
template< typename A >
inline void insert( std::stack<A>& c,  A const& v )
{
    c.push( v );
}

Graeme

---
[ 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: nesotto@cs.auc.dk ("THORSTEN OTTOSEN")
Date: Tue, 7 Jan 2003 18:55:11 +0000 (UTC)
Raw View
Hi,

I was writing some code when I discovered that Comeau could destinguish this
code (whereas GCC 3.2 and vc7 cannot):

 template< typename C, typename V >
 inline void insert( C& c, const V& v )
 {
     c.insert( c.end(), v );
 }

 template< typename A >
 inline void
 insert( A& c, const typename A::container_type::value_type& v )
 {
     c.push( v );
 }

It works with Comeau 4.3.0.1 and it is extreamly useful because it allows me
to leave out the inclusion of headers
like <stack> and beause it gives some kind of fake partial specialization of
function templates even though it
doesn't exist. (I guess Comeau could be wrong ). Anyway, consider this usage
scenario:

// file1.cpp
#include <vector>
...
vector<int> v;
insert( v, 4 );  // calls v.insert()

// file2.cpp
#include <stack>
...
stack<int> s;
insert( s, 6 ); // calls s.push()

pretty neat if I may say so; the latter version of insert() will only be
instantiated with classes like stack which defines
a container_type tydedef. My problem is then simply that cannot explain why
the latter version is a better
match than the first. I have tried looking at paragraph 13.3.3, 14.5.52 and
14.8.3 but could not find anything
that says that

template< typename C, typename V >
 inline void insert( C& c, const V& );

is a worse match than

 template< typename A >
 inline void
 insert( A& c, const typename A::container_type::value_type& v );

Is there some rule that "fewer template parameters is better" ?

--
Thorsten Ottosen, Aalborg University
nesotto@cs.auc.dk
---------------------------------------------------
C++:

my_map[key]++;

Java:

if ( !my_map.containsKey( key ) )
    my_map.put( key, new Integer( 1 ) );
else
{
    Integer count = ( Integer )my_map.get( key ) );
    int icount = count.IntValue();
    my_map.put( key, new Integer( ++icount ) );
}



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