This question got me thinking. Sometimes it's useful to grab an actual argument from a class template specialization, if it fails to define a public typedef
of the argument. In C++03 it's a sign of either bad template design, or contrary design intent, and not particularly common. But variadic templates make typedef coverage impossible, so it would be nice to have a tool around to solve the problem without additional work.
C++0x solves the typedef
problem for one particular variadic template, tuple
.
tuple_element< 2, tuple< char, short, int > >::type my_int; // nth element type
But tuple_element
isn't married to tuple
; it also works with pair
and array
. Its declaration doesn't mention tuple
.
template< size_t index, typename something_like_a_tuple >
struct tuple_element; // general case is left incomplete, unimplemented
tuple
is related by a partial specialization:
template< size_t index, typename ... tuple_elements >
struct tuple_element< index, tuple< tuple_elements ... > > { // impl. in here
But it doesn't need to be. A template template parameter could match tuple
, along any other template parameterized only over types.
template< size_t index,
template< typename ... > class template_over_types,
typename ... types >
struct tuple_element< index, template_over_types< types ... > > {
This would allow
tuple_element< 1, almost_any_template< char, int > >::type my_int;
tuple_element< 0, pair< int, char > >::type another_int; // same specialization
and yet allow the additional specialization for array
template< size_t index, typename element, size_t extent >
struct tuple_element< index, array< element, extent > >
{ typedef element type; }
No conflict is possible because array
's second argument is a size_t
, not a type.
Unfortunately, the user is allowed to specialize the tuple_element
interface for their own types. The user's precondition and their guarantee is given by C++0x §17.6.3.2.1/1:
A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.
So, not only must the general specialization not conflict with the array
specialization, it can't conflict with any specialization that names a user-defined type. That is, if the user declares a specialization, the existence of the general argument getter cannot affect whether it's chosen.
When ambiguity arises in instantiation (that is, two partial specializations match an argument list), the alternatives are compared to determine which is most specialized, in other words least generalized. Call the alternatives A and B. This means that, if A can do B's job, but B cannot do A's job, then B is more specialized. A is the generalist. B will be chosen. The actual arguments instigating the instantiation aren't considered, since they are already known to be a match to both candidates.
Since we want the general template to defer to everything else, we're in good shape.
Generality is checked by replacing the partial specialization parameters in A with unique dummy types, and checking whether B can also implement such a specialization. Repeat with the roles reversed, and if the opposite result is obtained, one candidate is known to be more specialized.
The existence of a user-defined type in the user's specialization guarantees its precedence, because there must be a corresponding unique dummy type in the argument-getter which won't match it.
For example, here is a very general user-declared specialization. It defines tuple_element
for any type-parameterized template containing a given user_type
.
template< size_t I,
template< typename ... > class any_template,
typename ... T, typename ... U >
struct tuple_element< I, any_template< T ..., ::user_type, U ... > >;
The sequence ..., user_type, ...
can be handled by the general case, but the user's case cannot handle a sequence composed entirely of artificial unique types, because it won't include user_type
.
If any user specialization is a candidate, it will be the superior one.
(The standard does specify in pseudocode a separate partial specialization for tuple
, but it could be omitted under the as-if rule. Anyway, if implemented, it would be covered by the same precedence rule as protects the user.)
I haven't made many forays into the partial ordering rules. Is this analysis correct? Is it OK for the implementation to expose a general template indexer through std::tuple_element
?