From: Paul Bibbings on
Mikosz <mikoszrrr(a)gmail.com> writes:

> Hi all,
>
> I've been wondering for a couple of days now - why won't the following
> code compile (fails on both gcc and comeau online):
>
> struct X
> {
> int t;
> template <typename T>
> struct Y
> {
> T t;
> };
> };
>
>
> template <typename T1, typename T2>
> struct A
> {
> };
>
> template<typename X, typename T>
> struct A< X, typename X::template Y<T> >
> {
> typedef int Type;
> };
>
>
> int main()
> {
> typedef X::Y<int> YI;
> A<X, YI>::Type t; // <- COMPILATION ERROR
> return 0;
> }

Taking your code as is, an analysis of why the class template
specialization A<X, YI> fails to select your partial specialization and
attempts to instantiate the primary template instead begins with:

[temp.class.spec.match] �14.5.4.1/1
"When a class template is used in a context that requires an
instantiation of the class, it is necessary to determine whether
the instantiation is to be generated using the primary template
or one of the partial specializations. This is done by matching
the template arguments of the class template specialization with
the template argument lists of the partial specializations.

- If exactly one matching specialization is found, the
instantiation is generated from that specialization.

- ...

- If no matches are found, the instantiation is generated from
the primary template."

The upshot of this is that your partial specialization is not considered
a match for A<X, YI>, that is, A<X, X::Y<int> > does not match:
template<typename X, typename T>
struct A< X, typename X::template Y<T> >;

and this because the process of "matching the template arguments of the
class template specialization [X and X::Y<int>] with the template
argument lists of the partial specialization [X and typename X::template
Y<T>, for template parameters X and T]" fails.

So:

[temp.class.spec.match] �14.5.4.1/2
"A partial specialization matches a given actual template argument
list if the template arguments of the partial specialization can
be deduced from the actual template argument list (14.8.2)."

In applying template argument deduction according to �14.8.2 the
following template function is formed from the partial specialization
(�14.5.4.2):

template<typename X, typename T>
void f(A<x, typename X::template Y<T> > { }

This function is used for the purposes of TAD from here in order
for any part of �14.8.2 (which treats template functions explicitly)
to apply.

Now, from [temp.deduct.type] �14.8.2.4/3 we have:

"In certain contexts, however, the value [of a template argument]
does not participate in type deduction, but instead uses the
values of template arguments that were either deduced elsewhere
or explicitly specified. If a template parameter is used only in
nondeduced contexts and is not explicitly specified, template
argument deduction fails."

then, from [temp.deduct.type]/4:

"The nondeduced contexts are:

- The nested-name-specifier of a type that was specified using a
qualified-id.

- //...

When a type name is specified in a way that includes a nondeduced
context, all of the types that comprise that type name are also
nondeduced."

and it is here that the failure in your code becomes apparent. In the
second of your template arguments, typename X::template Y<T>, X forms a
non-deduced context as "the nested-name-specifier of a type that was
specified using a qualified-id." (Note: X /can/ be deduced from the
first of your template arguments, so there is no problem, over all,
deducing X.) However, the fact that X in the context of the second
template argument requires that "all of the types that comprise that type
name are also nondeduced" means that *T remains nondeduced* in this
context and in the context of the specialization as a whole. Hence,
[temp.deduct.type] �14.8.2.4/2 is brought into play:

"...or if any template argument remains neither deduced nor
explicitly specified, template argument deduction fails."

and so, tracing back, by �14.5.4.1/2, matching of the class template
specialization to your partial specialization likewise fails. Then, by
�14.5.4.1/1, "the instantiation is generated from the primary template"
instead.

> The compilation error says that A::Type is undefined. Now, if you
> change the code, so that both versions of A contain a constant of the
> same name, but with different value and you'll print it out in main(),
> you'll find that the code does compile, but the unspecialised template
> version is chosen.

You may also see that the problem lies with the inability to deduce T by changing your partial specialization to:

template< typename X >
struct A< X, typename X::template Y< int > > { ... };

whereupon compilation succeeds and your partial specialization is now
selected.

> Since the templates compile just fine, it means that the specialised
> version is indeed a proper specialisation and is more detailed than
> the unspecialised one. How do I make the compiler select the
> specialised version then? If the construction is invalid and it's
> impossible to use the specialised version - why does the code compile
> at all?

I think the key point here is that the fact that it is impossible to
"use the specialized version" because T cannot be deduced does mean that
the construction is invalid. As the above analysis shows, there is no
sense in which the construction is ill-formed, it is merely the case
that it cannot be selected. This might seem surprising, but I suppose
it's no different than writing unreachable code along the lines of:

if (true)
// do something
else
// can't ever get here.

The above is not invalid, and in this instance your compiler should
happily warn you, it is just that you have written a valid construct in
a way that part of it is "unreachable," just as your partial
specialization is "uninstantiable."

> C++ gurus - what say you? :)

[Please note: I am far from being one of them. Treat all utterances
with caution :]

Regards

Paul Bibbings


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]