10

According to [temp.class.order] §14.5.5.2, the selection of a partial specialization of t in this example:

template< typename >
struct s { typedef void v, w; };

template< typename, typename = void >
struct t {};

template< typename c >
struct t< c, typename c::v > {};

template< typename c >
struct t< s< c >, typename s< c >::w > {};

t< s< int > > q;

is equivalent to the selection of an overload of f in this example:

template< typename >
struct s { typedef void v, w; };

template< typename, typename = void >
struct t {};

template< typename c >
constexpr int f( t< c, typename c::v > ) { return 1; }

template< typename c >
constexpr int f( t< s< c >, typename s< c >::w > ) { return 2; }

static_assert ( f( t< s< int > >() ) == 2, "" );

However, GCC, Clang, and ICC all reject the first example as ambiguous, but accept the second.

Even more strangely, the first example works if ::v is replaced with ::w or vice versa. The non-deduced contexts c:: and s< c >:: are apparently being considered in specialization ordering, which doesn't make sense.

Am I missing something in the standard, or do all these implementations have the same bug?

Barry
  • 286,269
  • 29
  • 621
  • 977
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421

3 Answers3

9

Switching to extremely-pedantic-mode for a moment, yes, I think you are missing something in the standard, and no, it shouldn't make any difference in this case.

All standard references are to N4527, the current working draft.

[14.5.5.2p1] says:

For two class template partial specializations, the first is more specialized than the second if, given the following rewrite to two function templates, the first function template is more specialized than the second according to the ordering rules for function templates (14.5.6.2):

  • the first function template has the same template parameters as the first partial specialization and has a single function parameter whose type is a class template specialization with the template arguments of the first partial specialization, and
  • the second function template has the same template parameters as the second partial specialization and has a single function parameter whose type is a class template specialization with the template arguments of the second partial specialization.

Going to [14.5.6.2p1]:

[...] Partial ordering of overloaded function template declarations is used in the following contexts to select the function template to which a function template specialization refers:

  • during overload resolution for a call to a function template specialization (13.3.3);
  • when the address of a function template specialization is taken;
  • when a placement operator delete that is a function template specialization is selected to match a placement operator new (3.7.4.2, 5.3.4);
  • when a friend function declaration (14.5.4), an explicit instantiation (14.7.2) or an explicit specialization (14.7.3) refers to a function template specialization.

No mention of partial ordering of class template specializations. However, [14.8.2.4p3] says:

The types used to determine the ordering depend on the context in which the partial ordering is done:

  • In the context of a function call, the types used are those function parameter types for which the function call has arguments.
  • In the context of a call to a conversion function, the return types of the conversion function templates are used.
  • In other contexts (14.5.6.2) the function template’s function type is used.

Even though it refers back to [14.5.6.2], it does say "other contexts". I can only conclude that, when applying the partial ordering algorithm to the function templates generated according to the rules in [14.5.5.2], the function template’s function type is used, not the list of parameter types, as it would happen for a function call.

So, the selection of a partial specialization of t in your first snippet would be equivalent not to a case involving a function call, but to one that takes the address of a function template (for example), which also falls under "other contexts":

#include <iostream>

template<typename> struct s { typedef void v, w; };
template<typename, typename = void> struct t { };

template<typename C> void f(t<C, typename C::v>) { std::cout << "t<C, C::v>\n"; }
template<typename C> void f(t<s<C>, typename s<C>::w>) { std::cout << "t<s<C>, s<C>::w>\n"; }

int main()
{
   using pft = void (*)(t<s<int>>);
   pft p = f;
   p(t<s<int>>());
}

(Since we're still in extremely-pedantic-mode, I rewrote the function templates exactly like the example in [14.5.5.2p2].)

Needless to say, this also compiles and prints t<s<C>, s<C>::w>. The chances of it producing different behaviour were slim, but I had to try it. Considering how the algorithm works, it would have made a difference if the function parameters were, for example, reference types (triggering the special rules in [14.8.2.4] in the case of a function call, but not in the other cases), but such forms can't occur with function templates generated from class template specializations.

So, this whole detour didn't help us one bit, but... it's a language-lawyer question, we had to have some standard quotes in here...


There are some active Core issues related to your example:

  • 1157 contains a note that I think is relevant:

    Template argument deduction is an attempt to match a P and a deduced A; however, template argument deduction is not specified to fail if the P and the deduced A are incompatible. This may occur in the presence of non-deduced contexts. Notwithstanding the parenthetical statement in 14.8.2.4 [temp.deduct.partial] paragraph 9, template argument deduction may succeed in determining a template argument for every template parameter while producing a deduced A that is not compatible with the corresponding P.

    I'm not entirely sure that's so clearly specified; after all, [14.8.2.5p1] says

    [...] find template argument values [...] that will make P, after substitution of the deduced values [...], compatible with A.

    and [14.8.2.4] references [14.8.2.5] in its entirety. However, it's pretty clear that partial ordering of function templates doesn't look for compatibility when non-deduced contexts are involved, and changing that would break a lot of valid cases, so I think this is just a lack of proper specification in the standard.

  • To a lesser extent, 1847 has to do with non-deduced contexts appearing in arguments to template specializations. It references 1391 for the resolution; I think there are some issues with that wording - more details in this answer.

To me, all of this says that your example should work.


Like you, I was quite intrigued by the fact that the same inconsistency is present in three different compilers. I got even more intrigued after I verified that MSVC 14 exhibits the exact same behaviour as the others. So, when I got some time, I thought I'd take a quick look at what Clang does; it turned out to be anything but quick, but it yielded some answers.

All the code relevant to our case is in lib/Sema/SemaTemplateDeduction.cpp.

The core of the deduction algorithm is the DeduceTemplateArgumentsByTypeMatch function; all variants of deduction end up calling it, and it's then used recursively to walk through the structure of compound types, sometimes with the help of the heavily overloaded DeduceTemplateArguments set of functions, and some flags to adjust the algorithm based on the specific type of deduction being done and the parts of a type's form being looked at.

An important aspect to note regarding this function is that it handles strictly deduction, and not substitution. It compares type forms, deduces template argument values for template parameters that appear in deduced contexts, and skips non-deduced contexts. The only other check it does is verifying that deduced argument values for a template parameter are consistent. I've written some more about the way Clang does deduction during partial ordering in the answer I mentioned above.

For partial ordering of function templates, the algorithm starts in the Sema::getMoreSpecializedTemplate member function, which uses a flag of type enum TPOC to determine the context for which partial ordering is being done; the enumerators are TPOC_Call, TPOC_Conversion, and TPOC_Other; self-explanatory. This function then calls isAtLeastAsSpecializedAs twice, back and forth between the two templates, and compares the results.

isAtLeastAsSpecializedAs switches on the value of the TPOC flag, makes some adjustments based on that, and ends up calling, directly or indirectly, DeduceTemplateArgumentsByTypeMatch. If that returns Sema::TDK_Success, isAtLeastAsSpecializedAs does only one more check, to verify that all template parameters that are used for partial ordering have values. If that's good too, it returns true.

And that's partial ordering for function templates. Based on the paragraphs quoted in the previous section, I was expecting partial ordering for class template specializations to call Sema::getMoreSpecializedTemplate with suitably constructed function templates and a flag of TPOC_Other, and everything to flow naturally from there. If that were the case, your example should work. Surprise: that's not what happens.

Partial ordering for class template specializations starts in Sema::getMoreSpecializedPartialSpecialization. As an optimization (red flag!), it doesn't synthesize function templates, but rather uses DeduceTemplateArgumentsByTypeMatch to do type deduction directly on the class template specializations themselves as the types of P and A. This is fine; after all, that's what the algorithm for function templates would end up doing anyway.

However, if all goes well during deduction, it then calls FinishTemplateArgumentDeduction (the overload for class template specializations), which does substitution and other checks, including checking that the substituted arguments to the specialization are equivalent to the original ones. This would be fine if the code were checking whether a partial specialization matches a set of arguments, but is not fine during partial ordering, and, as far as I can tell, causes the problem with your example.

So, it seems that Richard Corden's assumption as to what happens is correct, but I'm not entirely sure this was intentional. This looks more like an oversight to me. How we ended up with all compilers behaving the same way remains a mystery.

In my opinion, removing the two calls to FinishTemplateArgumentDeduction from Sema::getMoreSpecializedPartialSpecialization would do no harm, and would restore consistency to the partial ordering algorithm. There's no need for the additional check (done by isAtLeastAsSpecializedAs) that all template parameters have values either, as we know all template parameters are deducible from the specialization's arguments; if they weren't, the partial specialization would fail matching, so we wouldn't get to partial ordering in the first place. (Whether such partial specializations are allowed in the first place is the subject of issue 549. Clang issues a warning for such cases, MSVC and GCC issue an error. Anyway, not a problem.)

As a side note, I think all of this applies to the overload for variable template specializations as well.

Unfortunately, I don't have a build environment set up for Clang, so I can't test this change at the moment.

Community
  • 1
  • 1
bogdan
  • 9,229
  • 2
  • 33
  • 48
2

I feel the intent is that the examples compile, however, the standard doesn't clearly state what should happen (if anything) when matching of template argument lists for the synthesized argument lists used by partial ordering (14.5.5.1/1):

This is done by matching the template arguments of the class template specialization with the template argument lists of the partial specializations.

The above paragraph is required so that #1 is selected in the following:

template <typename T, typename Q> struct A;
template <typename T>             struct A<T, void> {}; #1
template <typename T>             struct A<T, char> {}; #2

void foo ()
{
  A<int, void> a;
}

Here:

  1. The template parameter T is deduced to int (14.5.5.1/2)
  2. The resulting argument lists match: int == int, void == void (14.5.5.1/1)

For the partial ordering case:

template< typename c > struct t< c, typename c::v > {};  #3
template< typename c > struct t< s< c >, typename s< c >::w > {}; #4

For the first parameter, #4 is more specialized and both second parameters are non-deduced contexts, ie. type deduction succeeds for #4 to #3 but not for #3 to #4.

I think the compilers are then appling the "argument lists must match" rule from 14.5.5.1/1 on the synthesized argument lists. This compares the first synthesized type Q1::v to the second s<Q2>::w and these types are not the same.

This may explain why changing v to w resulted in some examples working, as the compiler decided those types were the same.

This is not an issue outside of partial ordering, because the types are concrete as types like c::v will be instantiated to void etc.

It could be that the committee intends for the type equivalence (14.4) to apply, but I don't think so. The standard should probably just clarify exactly what should happen regarding matching (or not) of synthesized types created as part of the partial ordering step.

Richard Corden
  • 21,389
  • 8
  • 58
  • 85
  • 1
    Right. In a nutshell, in partial deduction these compilers are substituting fake types into non-deduced contexts and then using them for deduction. That can never end well. In function call deduction, non-deduced contexts are ignored during deduction and substituted either before or after. (Sorry about the late reply, I missed the new-answer notification.) – Potatoswatter Jul 31 '15 at 01:41
1

The information in this answer is based in large part off of this question. The template partial ordering algorithm is underspecified by the standard. The main compilers seem to at least agree on what the algorithm should be though.


To start with, your two examples aren't equivalent. You have two template specializations in addition to your primary template, but with your function example, you're not adding a function overload for the primary. If you add it:

template <typename c>
constexpr int f( t<c> ) { return 0; } 

The function call becomes ambiguous as well. The reason for this is that partial ordering type synthesis algorithm does not instantiate templates and instead synthesizes new unique types.

First, if we compare the function I just introduced to this one:

template< typename c >
constexpr int f( t< c, typename c::v > ) { return 1; }

We have:

+---+---------------------+----------------------+
|   | Parameters          | Arguments            |
+---+---------------------+----------------------+
| 0 | c, typename c::v    | Unique0, void        |
| 1 | c, void             | Unique1, Unique1_v   |
+---+---------------------+----------------------+

We ignore non-deduced contexts in the partial ordering deduction rules, so Unique0 matches c, but Unique1_v does not match void! Thus 0 is preferred. This is probably not what you expected.

If we then compare 0 and 2:

+---+--------------------------+----------------------+
|   | Parameters               | Arguments            |
+---+--------------------------+----------------------+
| 0 | s<c>, typename s<c>::w   | Unique0, void        |
| 2 | c, void                  | Unique2, Unique2_v   |
+---+--------------------------+----------------------+

Here, the 0 deduction fails (since Unique0 won't match s<c>), but the 2 deduction also fails (since Unique2_v won't match void). That's why it's ambiguous.


This lead me to an interesting question about void_t:

template <typename... >
using void_t = void;

This function overload:

template< typename c >
constexpr int f( t< s< c >, void_t<s<c>>> ) { return 3; }

would be preferred over 0, since the arguments would be s<c> and void. But this one would not be:

template <typename... >
struct make_void {
    using type = void;
};

template< typename c >
constexpr int f( t< s< c >, typename make_void<s<c>>::type> ) { return 4; }

Since we wouldn't instantiate make_void<s<c>> in order to determine ::type, so we end up in the same situation as 2.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • `template constexpr int f( t ) { return 0; }` is not equivalent to the primary template. That is ambiguous with `t` because `c::v` is the same as the default argument, `void`. The function corresponding to the primary is `template< typename c, typename d > constexpr int f( t< c, d > ) { return 0; }`, but that doesn't enter into partial specialization because partial specializations *must* be more specialized than the primary in the first place. – Potatoswatter Jul 19 '15 at 13:41
  • Sorry, but [language-lawyer] questions need to be backed up by more than citation of 6-year-old SO answers. Even if SO were authoritative, things might have changed since then. Assertions such as that the "partial ordering type synthesis algorithm does not instantiate templates and instead synthesizes new unique types" may be true, but you should cite the text which would say one way or another. To be sure, the compiler seems to be using a different algorithm between functions and partial specializations, which is at least inconsistent. – Potatoswatter Jul 19 '15 at 13:53
  • There's no text in the standard one way or the other. It's underspecified. And the primary template is `t`, not `t`. – Barry Jul 19 '15 at 15:24
  • I sympathize with the frustration as I asked a [similar question](http://stackoverflow.com/q/31394260/2069064) recently which likewise has no satisfactory answer. – Barry Jul 19 '15 at 15:26
  • @Potatoswatter For instance, the partial ordering rules only ever talk about pairwise deduction. So there's nothing in there that specifies that `is_same` is more specialized than `is_same`. Which is weird. – Barry Jul 19 '15 at 16:37
  • 1. The primary template is `template< typename, typename > struct t` and it has a default template-argument. 2. Then why add an unsatisfactory answer here? — and no answer after one week does not suggest that none exists. 3. Yes, it does: one unique argument is synthesized for each parameter in {`T`,`U`} and matching fails against the transformed `is_same`. Note that partial specializations are only checked against the primary once, upon their declaration. – Potatoswatter Jul 20 '15 at 00:29
  • @Potatoswatter At least (3) I can cite. [temp.deduct.type] states "Type deduction is done independently for each P/A pair, and the deduced template argument values are then combined." However, [temp.deduct.partial] only talks about each P/A pair independently and never talks about combining the results. – Barry Jul 20 '15 at 01:01
  • there is only ever one P/A pair in partial specialization, because it constructs functions with one parameter. The way you constructed the examples in this answer is completely wrong. [temp.class.order]/1: "the {first/second} function template has … a single function parameter…" – Potatoswatter Jul 20 '15 at 02:15
  • 1
    Ah, right, same argument but with `is_same(T, T)` vs `is_same(T, U)`. – Barry Jul 20 '15 at 02:19