# 'Ambiguous partial specializations and enable_if_t

This question is due to insane curiosity rather than an actual problem. Consider the following code:

```
template<typename...>
struct type_list {};
template<typename, typename = void>
struct test_class;
template<typename... T>
struct test_class<type_list<T...>> {
static constexpr auto value = false;
};
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
int main() {
static_assert(!test_class<type_list<double, char>>::value);
static_assert(test_class<type_list<int>>::value);
}
```

This fails with the error:

ambiguous partial specializations of 'test_class<type_list>'

If I changed the second specialization to something that *doesn't work* from a functional point of view, the error would go away:

```
template<typename T>
struct test_class<type_list<T>> {
static constexpr auto value = true;
};
```

Similarly, if I use the alias template `void_t`

, everything works as expected:

```
template<typename T>
struct test_class<type_list<T>, std::void_t<std::enable_if_t<std::is_same_v<T, int>>>> {
static constexpr auto value = true;
};
```

Apart from the ugliness of combining `void_t`

and `enable_if_t`

, this also gets the job done when there is a single type that differs from `int`

, ie for a `static_assert(!test_class<type_list<char>>::value)`

(it wouldn't work in the second case instead, for obvious reasons).

I see why the third case *works-ish*, since the alias template is literally *replaced* with `void`

when the condition of the `enable_if_t`

is satisfied and `type_list<T>`

is more specialized than `type_list<T...>`

(right?).
However, I would have expected the same also for the following:

```
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
```

At the end of the day, `std::enable_if_t<std::is_same_v<T, int>>`

somehow **is** `void`

when the condition is statisfied (ok, technically speaking it's `typename blabla::type`

, granted but isn't `::type`

still `void`

?) and therefore I don't see why it results in an ambiguous call. I'm pretty sure I'm missing something obvious here though and I'm curious to understand it now.

I'd be glad if you could point out the *standardese* for this and let me know if there exists a nicer solution than combining `void_t`

and `enable_if_t`

eventually.

## Solution 1:^{[1]}

Let's start with an extended version of your code

```
template<typename, typename = void>
struct test_class;
template<typename T>
struct test_class<type_list<T>> {
static constexpr auto value = false;
};
template<typename... Ts>
struct test_class<type_list<Ts...>> {
static constexpr auto value = false;
};
template<typename T>
struct test_class<type_list<T>, std::enable_if_t<std::is_same_v<T, int>>> {
static constexpr auto value = true;
};
```

that is called with

```
test_class<type_list<int>>::value
```

The standard distinguishes between template parameters that are equivalent, ones that are only functionally equivalent and others which are not equivalent [temp.over.link]/5

Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one-definition rule, except that the tokens used to name the template parameters may differ as long as a token used to name a template parameter in one expression is replaced by another token that names the same template parameter in the other expression. Two unevaluated operands that do not involve template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one-definition rule, except that the tokens used to name types and declarations may differ as long as they name the same entities, and the tokens used to form concept-ids may differ as long as the two template-ids are the same ([temp.type]).

Two potentially-evaluated expressions involving template parameters that are not equivalent are functionally equivalent if, for any given set of template arguments, the evaluation of the expression results in the same value. Two unevaluated operands that are not equivalent are functionally equivalent if, for any given set of template arguments, the expressions perform the same operations in the same order with the same entities.

E.g. `std::enable_if_t<std::is_same_v<T, T>>`

and `void`

are only functionally equivalent: The first term will be evaluated to `void`

for any template argument `T`

. This means according to [temp.over.link]/7 code containing two specialisations `<T, void>`

and `<T, std::enable_if_t<std::is_same_v<T, T>>`

is already ill-formed:

If the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required.

In the code above ** std::enable_if_t<std::is_same_v<T, int>>** is not even functionally equivalent to any of the other versions as it is in general

**not equivalent to**.

`void`

When now performing **partial ordering** [temp.func.order] to see which specialisation matches best your call this will result in an **ambiguity** as `test_class`

is equally specialised [temp.func.order]/6 in both cases (with either `Ts={int}, void`

or `T=int, std::enable_if_t<std::is_same_v<T, int>>`

, both resulting in `int, void`

but you can't deduce them against each other) and therefore the compilation will fail.

On the other hand by wrapping `std::enable_if_t`

with `std::void_t`

, which is nothing more but an alias for void

```
template <typename T>
using void_t = void;
```

the partial ordering will succeed as in this case the compiler will already know the type of the last template parameter is `void`

in all cases, choosing `test_class<T, std::void_t<std::enable_if_t<std::is_same_v<T,int>>>>`

with `T=int`

as the most specialised as the non-variadic case (`T=int, void`

) is considered more specialised than the variadic one `Ts={int}, void`

(see e.g. temp.deduct.partial/8 or again this).

## Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution | Source |
---|---|

Solution 1 |