SFINAE revisited

J

James Kanze

Just ran into an interesting question concerning SFINAE. Given
the following code:

#include <iostream>
#include <typeinfo>

template< typename T >
class P
{
public:
P( T* ) { std::cout << "In P: normal ctor" << std::endl ; }
template< typename U >
P( U* ) { std::cout << "In P: template ctor (U = "
<< typeid( U ).name() << ')' << std::endl ; }
} ;

class B
{
public:
virtual ~B() {}
} ;

int
main()
{
class D : public B {} ;
P< B > p( new D ) ;
return 0 ;
}

Should this compile, and if so, what should the program output?
I've got three different compilers at hand: one fails to
compile, one outputs "In P: normal ctor", and one outputs "In P:
template ctor (U = ...)", where the ... is the mangled name of
D. (My own feeling is that it shouldn't compile. But I'm not
all that clear about template argument deduction, and at what
level SFINAE kicks in.)
 
J

joseph cook

Just ran into an interesting question concerning SFINAE.  Given
the following code:

    #include <iostream>
    #include <typeinfo>

    template< typename T >
    class P
    {
    public:
        P( T* ) { std::cout << "In P: normal ctor" << std::endl ; }
        template< typename U >
        P( U* ) { std::cout << "In P: template ctor (U = "
                    << typeid( U ).name() << ')' << std::endl ; }
    } ;

    class B
    {
    public:
        virtual             ~B() {}
    } ;

    int
    main()
    {
        class D : public B {} ;
        P< B > p( new D ) ;
        return 0 ;
    }

Should this compile, and if so, what should the program output?
I've got three different compilers at hand: one fails to
compile, one outputs "In P: normal ctor", and one outputs "In P:
template ctor (U = ...)", where the ... is the mangled name of
D.  (My own feeling is that it shouldn't compile.  But I'm not
all that clear about template argument deduction, and at what
level SFINAE kicks in.)


Is this any different than having the same set-up with functions
instead of constructors?

template<typename T>
class Foo
{
public:
void goo(T* a) { std::cout << "In Foo: normal fn" << std::endl ;}
template<typename U>
void goo(U* a) { std::cout <<"Int Foo: templated fn"<<std::endl;
};

class B { public: virtual ~B(){} };
class D : public B{};

int main()
{
Foo<B> foo;
foo.goo(new D);
}

I would think this last line reliably (and rightfully) always chooses
the templated function, but if replaced by "new B" would pick the
"normal fn".

I don't see why Constructors should behave differently (I'd bd curious
if your "three compilers" behave differently in my example.

Joe Cook
 
G

Gennaro Prota

James said:
Just ran into an interesting question concerning SFINAE. Given
the following code:

#include <iostream>
#include <typeinfo>

template< typename T >
class P
{
public:
P( T* ) { std::cout << "In P: normal ctor" << std::endl ; }
template< typename U >
P( U* ) { std::cout << "In P: template ctor (U = "
<< typeid( U ).name() << ')' << std::endl ; }
} ;

class B
{
public:
virtual ~B() {}
} ;

int
main()
{
class D : public B {} ;
P< B > p( new D ) ;
return 0 ;
}

Should this compile, and if so, what should the program output?
I've got three different compilers at hand: one fails to
compile, one outputs "In P: normal ctor", and one outputs "In P:
template ctor (U = ...)", where the ... is the mangled name of
D. (My own feeling is that it shouldn't compile. But I'm not
all that clear about template argument deduction, and at what
level SFINAE kicks in.)

First a question: was it intentional that D is a local class? That's
the only reason I see for it not to compile (modulo N2580 :)).
 
J

James Kanze

Is this any different than having the same set-up with functions
instead of constructors?

Probably not. I just happened to run into it with constructors.
template<typename T>
class Foo
{
public:
void goo(T* a) { std::cout << "In Foo: normal fn" << std::endl ;}
template<typename U>
void goo(U* a) { std::cout <<"Int Foo: templated fn"<<std::endl;
};
class B { public: virtual ~B(){} };
class D : public B{};
int main()
{
Foo<B> foo;
foo.goo(new D);
}
I would think this last line reliably (and rightfully) always
chooses the templated function, but if replaced by "new B"
would pick the "normal fn".

Certainly, but you've changed one very important part; in my
code, the derived class is local, which means that it's illegal
to instantiate a template over it.
I don't see why Constructors should behave differently (I'd bd
curious if your "three compilers" behave differently in my
example.

I doubt they do, but your example has nothing to do with my
question.
 
J

James Kanze

First a question: was it intentional that D is a local class?

Obviously. Otherwise, there's no reason for it not to compile,
and systematically chose the template constructor.
That's the only reason I see for it not to compile (modulo
N2580 :)).

I'll have a glance at the paper you cite, but the heart of the
question, of course, is what happens when template argument
deduction comes up with a local class? Is it substitution
failure (in which case, the function is simply dropped, and the
compiler chooses the non-template), or is the substitution
successful, overload resolution then chooses the template
(because it is a better match) and you get an error, because
it's illegal to instantiate the template with a local class.
(The compiler which calls the template function is obviously
wrong.)
 
F

Fraser Ross

"Gennaro Prota"
First a question: was it intentional that D is a local class? That's
the only reason I see for it not to compile (modulo N2580 :)).


I think it is intentional. The template function is a perfect match
only if D is not local to main and so has linkage. The non-template
function is the only one considered otherwise, and is a match with an
implicit conversion.

Fraser.
 
C

courpron

Just ran into an interesting question concerning SFINAE.  Given
the following code:

    #include <iostream>
    #include <typeinfo>

    template< typename T >
    class P
    {
    public:
        P( T* ) { std::cout << "In P: normal ctor" << std::endl ; }
        template< typename U >
        P( U* ) { std::cout << "In P: template ctor (U = "
                    << typeid( U ).name() << ')' << std::endl ; }
    } ;

    class B
    {
    public:
        virtual             ~B() {}
    } ;

    int
    main()
    {
        class D : public B {} ;
        P< B > p( new D ) ;
        return 0 ;
    }

Should this compile, and if so, what should the program output?
I've got three different compilers at hand: one fails to
compile, one outputs "In P: normal ctor", and one outputs "In P:
template ctor (U = ...)", where the ... is the mangled name of
D.  (My own feeling is that it shouldn't compile.  But I'm not
all that clear about template argument deduction, and at what
level SFINAE kicks in.)


In your example, a local class is used as a (deduced) template
argument. Local classes can't be used as a template argument. SFINAE
comes when type deduction fails. The C++ standard lists exhaustively
the cases where type deduction fails. The use of a local class as a
template argument is not listed as one of those cases.

Alexandre Courpron.
 
J

James Kanze

SFINAE would kick in if the type deduction would fail due to the
resulting type (or some other element derived from that) is not a
"natural" C++ element, but not an explicitly stated cause of
ill-formedness. OTOH, the Standard is a bit vague WRT using local types
with templates. If you subscribe to the strict spirit of the Standard,
then *any* use of local type as a type argument of a template is not
allowed (according to [temp.arg.type]/2), so the constructor P(U*) that
is instantiated when 'U == D', is an illegal (but not unnatural)
construct. In that case SFINAE does not apply. If you subscribe to the
relaxed Standard, then you shall only avoid using local types when
specifying the template type argument explicitly, but not when you allow
it to be deduced from a function call. In that case SFINAE doesn't
apply either (since the deduction cannot "fail"), and the templated
c-tor is simply used with 'U == D'. Comeau behaves according to the
former strategy, VC++ - according to the latter.

I think that the standard is clear (unless there have been
clarifications I've missed): you can't instantiate a template
over a local class. But you've pretty much summed up the crux
of the problem (at least as I see it): is this a substitution
failure, or does the substitution work, overload resolution pick
up the local class, and then you get an error, because
instantiation over a local class is illegal. That's the opinion
of Sun CC, and a fairly quick reading of the original standard
seems to agree with them. But it's all vague enough, and my
knowledge of this area of the standard is weak enough, that I'm
really not sure.

(FWIW: the problem showed up with a bit of code which was
developed with g++, under Linux. And which passed all of my
tests. Until I ported it to Sun CC under Solaris:). After
looking at it, my first reaction was that Sun CC was right, and
how did g++ miss this. And then SFINAE occured to me; if there
was a substitution failure, g++ was right. I gave VC++ a try,
since I had access to it, to get a deciding vote, but it took
yet another path---the one you describe. And on reading the
standard, I was even less sure.)
 
C

courpron

Really?  I thought that the Standard actually listed exhaustively the
cases where type deduction *succeeded*.  Everything else fails.  Where
is the exhaustive list of failures?  Just point me to the paragraph, no
need to quote it.

Explicit template argument :
14.8.2/2

Deduced template argument :
14.8.2.4 (the whole section, specifically paragraphs 11 to 18)


Alexandre Courpron.
 
F

Fraser Ross

"James Kanze"
I think that the standard is clear (unless there have been
clarifications I've missed): you can't instantiate a template
over a local class.

I feel that the compiler should not attempt to instantiate with a local
class. After overload resolution failure a diagnostic might refer to
the local class. The Borland compiler gets everything right as I see
it. The Comeau compiler reports an error too soon for type deduction
failure (in broad terms) and strangely twice.

Fraser.
 
G

Gennaro Prota

James said:
Obviously. Otherwise, there's no reason for it not to compile,
and systematically chose the template constructor.


I'll have a glance at the paper you cite,

Hmm :) Then don't beat me!
> but the heart of the
question, of course, is what happens when template argument
deduction comes up with a local class? Is it substitution
failure (in which case, the function is simply dropped, and the
compiler chooses the non-template), or is the substitution
successful, overload resolution then chooses the template
(because it is a better match) and you get an error, because
it's illegal to instantiate the template with a local class.
(The compiler which calls the template function is obviously
wrong.)

OK. I see now. I just stopped when I saw the local class and thought
to ask before digging further.

Right now I can't check the standard with the care it deserves, but I
believe it boils down to this: substitution (deduction) *succeeds*,
because "usage of local class for template type argument" isn't listed
as a case of substitution failure; so, a point of instantiation is
created for both constructors, and that leads to an error for each
constructor. This is before there's any chance to add the
instantiations to the overload set, and thus there's really no
overload resolution. But all this should be backed up with a few
paragraph numbers from the standard :) I'll try doing that tomorrow.
 
J

Joe Greer

"James Kanze"
I think that the standard is clear (unless there have been
clarifications I've missed): you can't instantiate a template
over a local class.

I feel that the compiler should not attempt to instantiate with a local
class. After overload resolution failure a diagnostic might refer to
the local class. The Borland compiler gets everything right as I see
it. The Comeau compiler reports an error too soon for type deduction
failure (in broad terms) and strangely twice.

Fraser.

I am far from an expert, but it seems to me that it was explicitly
instantiated with a class B (which is not local, but passed in a D * as an
argument with a conversion to B *). I could really seeing going either
way.

joe
 
G

Gennaro Prota

Gennaro said:
Right now I can't check the standard with the care it deserves, but I
believe it boils down to this: substitution (deduction) *succeeds*,
because "usage of local class for template type argument" isn't listed
as a case of substitution failure; so, a point of instantiation is
created for both constructors, and that leads to an error for each
constructor. This is before there's any chance to add the instantiations
to the overload set, and thus there's really no overload resolution. But
all this should be backed up with a few paragraph numbers from the
standard :) I'll try doing that tomorrow.

I couldn't resist checking at least the core issues :) This should be
useful:

<http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#488>
 
C

courpron

OK, perhaps my understanding of English is failing me, and that's why
when I see
  "If a substitution in a template parameter or in the function type
   of the function template results in an invalid type, type deduction
   fails."
I just conclude that this is all-inclusive rule, deduction resulting in
an invalid type fails.  The last sentence of the third bullet item in
the paragraph 2 says "Type deduction *may* fail for the following
reasons:" (with a list of the causes that can make deduction fail, the
emphasis mine).  To me it sounds too much like an extension of the note
it follows, as if it says, "Oh, BTW, here is a list of what *may* happen
among other things".  BTW, the use of "may" is very tricky AFA standards
are concerned.  There is no certainty in it when it comes to (in this
particular case) positive behavior.  It opens the door to the deduction
that is allowed to succeed in any of those enumerated situations.  For
example, the compiler is *allowed* to deduce the argument even if it
results in a pointer to a reference, if such pointer is not used...
That is probably why I never considered those lists to be exhaustive
enumerations of what is to be treated as the reason for a failure.


Yes, "may" is often the word where divergences of interpretations
start. It makes translation of standards in some other languages
tricky.

However, the section you are refering to concerns *explicit* template
arguments. In our case (deduced template argument) the relevant
section is 14.8.2.4 (specifically /11 to /18 for deduction failures),
which doesn't contain an all-inclusive rule.

Alexandre Courpron.
 
J

joseph cook

Obviously.  Otherwise, there's no reason for it not to compile,
and systematically chose the template constructor.


I'll have a glance at the paper you cite,

That should be easy since you wrote it.

Joe Cook
 
F

Fraser Ross

"Joe Greer"
I am far from an expert, but it seems to me that it was explicitly
instantiated with a class B (which is not local, but passed in a D * as an
argument with a conversion to B *). I could really seeing going either
way.

Its explicitly instantiated as so:
P< B >

The type of "new D" is unimportant in deciding the type of T. Are you
aware that there isn't implicit conversions involved in template
argument deduction? U can't be B.

Fraser.
 
G

Gennaro Prota

Gennaro said:
Right now I can't check the standard with the care it deserves, but
I believe it boils down to this: substitution (deduction) *succeeds*,
because "usage of local class for template type argument" isn't listed
as a case of substitution failure; so, a point of instantiation is
created for both constructors, and that leads to an error for each
constructor. This is before there's any chance to add the instantiations
to the overload set, and thus there's really no overload resolution. But
all this should be backed up with a few paragraph numbers from the
standard :) I'll try doing that tomorrow.

Sorry, of course that's *one* error, not an error for each
constructor; I should refrain from posting at the end of a day like
yesterday was. Apart from that oversight, I think the analysis is
correct.

The text of core issue 488, which I linked to in the other post,
already mentions the relevant sections of the standard, particularly
14.8.2 [temp.deduct] par. 2 and the fact that substituting a local
type isn't listed there as a SFINAE case. (In and of itself that
section concerns the case of explicitly specified template arguments,
but then the deduced case is re-conducted back to the former). Just
let me add that we could just write

class B;

class P
{
public:
P( B * );
template< typename U >
P( U * );
} ;

class B
{
public:
virtual ~B() {}
} ;

int
main()
{
class D : public B {} ;
P p( new D ) ;
return 0 ;
}

(i.e. make P an ordinary class; who needs smart *P*ointers to be
templates :)) and still have the same problem (in C++03):
substitution works and an ill-formed declaration is then
instantiated.

PS: VC++ should get this right with /Za. If it doesn't then this is a
regression with respect to at least VC++ 2005 (I'm assuming you are
using 2008).
 
J

James Kanze

@news.astraweb.com:
I am far from an expert, but it seems to me that it was
explicitly instantiated with a class B (which is not local,
but passed in a D * as an argument with a conversion to B *).
I could really seeing going either way.

The type of the object is clear. The problem is in the
resolution of the call to the constructor, and how type
deduction works.

From other postings, I gather that the committee has fixed this
by allowing instantiation over local types. And I gather that
there was more or less a consensus in the committee (or at least
those members addressing the issue), that the code was illegal
according to the original standard.
 
G

Gennaro Prota

James Kanze wrote:
[...]
So it is. It also has absolutely nothing to do with the
question at hand. Since Gennaro put a smiley there, I suppose
the reference was meant as some sort of a joke. Or maybe he
simply mistyped the number.

Yes, it was meant as a joke (sorry if it failed to be so), since
you've longly been in the (correct) habit of including both <ostream>
and <iostream> in such cases.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,818
Latest member
Brigette36

Latest Threads

Top