简体   繁体   中英

Can a single member of a class template be partially specialized?

I came across an interesting point that I wasn't able to explain or find an explanation for. Consider the following template definition (compiled with mingw g++ 4.6.2):

template <typename T, typename S>
class Foo
{
public:
    void f(){}
    void g(){}
};

Should we want to, we can fully specialize any single member function:

template <>
void Foo<char,int>::f() {}

But partial specialization fails with an "invalid use of incomplete type 'class Foo<...>'" error:

template <typename T, typename S>
void Foo<T,S*>::f()
{
}

template <typename T>
void Foo<T,int>::f()
{
}

And I can't figure out why. Is it a conscious design decision made to avoid some problem I can't foresee? Is it an oversight?

The notion of partial specialization only exists for class templates (described by §14.5.5) and member templates (ie members of a template class that are themselves template functions, described by §14.5.5.3/2). It does not exist for ordinary members of class templates, nor does it exist for function templates – simply because it is not described by the Standard.

Now, you might argue that by giving the definition of a partial specialization of a member function, such as

template <typename T>
void Foo<T,int>::f()
{ }

you implicitly define a partial specialization of the class template: Foo<T,int> . That, however, is explicitly ruled out by the Standard:

(§14.5.5/2) Each class template partial specialization is a distinct template and definitions shall be provided for the members of a template partial specialization (14.5.5.3).

(§14.5.5.3/1) [...] The members of the class template partial specialization are unrelated to the members of the primary template. Class template partial specialization members that are used in a way that requires a definition shall be defined; the definitions of members of the primary template are never used as definitions for members of a class template partial specialization. [...]

The latter implies that it is impossible to implicitly define a partial specialization by simply giving the definition of one of its members: The very existence of that member would not follow from the definition of the primary template, hence defining it is equivalent to defining a member function that wasn't declared , and that isn't allowed (even with non-template classes).

On the other hand, the notion of explicit specialization (or full specialization , as you call it) exists for member functions of class templates. It is explicitly described by the Standard:

(§14.7.3/1) An explicit specialization of any of the following:
[...]
— member function of a class template
[...]
can be declared by a declaration introduced by template<>; [...]

§14.7.3/14 describes the details:

(§14.7.3/14) A member or a member template of a class template may be explicitly specialized for a given implicit instantiation of the class template, even if the member or member template is defined in the class template definition. [...]

Hence, for explicit specializations of members, the instantiation of the rest of the class template works implicitly – it is derived from the primary template definition, or any partial specializations if defined.

I tried to find a succinct quote from the standard, but I don't think there is one. The fact is, there is no such thing as a partial specialization of a template function (or, for that matter, of a template alias). Only class templates can have partial specializations.

Let's forget about templates for a second. In C++, there is a big difference between class names and function names. There can only be one definition of a class within a given scope. (You can have various declarations, but they all refer to the One True Class.) So the name really identifies the class.

A function name, on the other hand, is a kind of group identity. You can define any number of functions within a scope with exactly the same name. When you use a function name to call a function, the compiler has to figure out which function you really meant by looking at the various possibilities and matching the signature of each of them with the supplied arguments. There's no relationship between the various functions which share a name; they're completely separate entities.

So, no big deal. You knew all this, right? But now let's go back to templates.

The name of a templated class is still unique. Although you can define partial specializations, you have to explicitly specialize the same templated class. This mechanism looks superficially like the function-name resolution algorithm referred to above, but there are significant differences -- one of them is that, unlike function prototypes, you cannot have two class templates in the same scope with different kinds of template parameters.

Templated functions, on the other hand, have no need to define unique names. Templating does not replace the normal function overload mechanism. So when the compiler is trying to figure out what a function name means, it has to consider all templated and non-templated declarations for that function name, resolve the templated ones to a set of template parameter assignments (if possible) and then once it has a list of possible function objects, select the best one with normal overload resolution.

That's quite a different algorithm from the templated class template parameter resolution. Instead of just matching a list of provided template arguments with a list of declared template parameters, which is how it resolves class templates, it has to take each templated function which might possibly match (has at least the right number of parameters, for example); deduce template parameters by unifying the supplied arguments with the template; and then add the resolve specialization to the overload set for a further round of overload resolution.

I suppose it would have been possible to have added partial specialization resolution into that process as well, but the interactions between partial specialization and function overloading strike me as likely to lead to pseudo-magical behaviour. In the event, it wasn't necessary and so there is no such mechanism. (You can fully specialize a function template. Full specialization means that there are no template arguments to deduce, so it's not a problem.)

So that's the scoop: you can't partially specialize a templated function, but there is nothing stopping you from providing any number of function templates with the same name. All of them will be considered in overload resolution, and the best one will win, as usual.

Usually, that's actually sufficient for your overloading needs. You should think about templated functions just the same way you think about normal functions: come up with a way to select the one you want based on the supplied arguments. If you feel you really need to supply template parameters in a function call, rather than having them be deduced, just make the function a (possibly static) member of a templated class, and supply the template arguments to the class.

Hope that helps...

I think that the difference is that when you do the first (valid) explicit specialization of f :

template <>
void Foo<char,int>::f() {}

You are doing an implicit instantiation of Foo<char,int> . But when you try the partial specialization with:

template <typename T>
void Foo<T,int>::f()
{
}

The compiler would need to instantiate implicitly Foo<T,int> before doing the specialization, but it cannot do that because of the T . And it fails.

You can check that is the case with the following code:

template <typename T, typename S>
class Foo
{
public:
    void f(){}
    void g(){}
};


template <>
void Foo<char,int>::f() //line 11
{}

template <>
class Foo<char,int> //line 15
{};

With g++ it gives the errors:

test.cpp:15:7: error: specialization of ‘Foo<char, int>’ after instantiation
test.cpp:15:7: error: redefinition of ‘class Foo<char, int>’
test.cpp:2:7: error: previous definition of ‘class Foo<char, int>’

With clang++ is a bit clearer:

test.cpp:15:7: error: explicit specialization of 'Foo<char, int>' after instantiation
class Foo<char,int>
      ^~~~~~~~~~~~~
test.cpp:11:6: note: implicit instantiation first required here
void Foo<char,int>::f() 
     ^

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM