According to [temp.class.order] §14.5.5.2, the selection of a partial specialization of t
in this example:
template< typename >
struct s {
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
constexpr int f( t ) { 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, typename s::w | Unique0, void |
| 2 | c, void | Unique2, Unique2_v |
+---+--------------------------+----------------------+
Here, the 0
deduction fails (since Unique0
won't match s
), 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
using void_t = void;
This function overload:
template< typename c >
constexpr int f( t< s< c >, void_t>> ) { return 3; }
would be preferred over 0
, since the arguments would be s
and void
. But this one would not be:
template
struct make_void {
using type = void;
};
template< typename c >
constexpr int f( t< s< c >, typename make_void>::type> ) { return 4; }
Since we wouldn't instantiate make_void
in order to determine >::type
, so we end up in the same situation as 2
.