4

I have a program that is as follows. There is a base template struct X and a partial specialisation with SFINAE.

template <typename T, typename U = void>
struct X{
  X() {
    std::cout << "in 1" << std::endl;
  };
};

template <typename T>
struct X< T, std::enable_if_t<std::is_integral_v<T>> > {
  X() {
    std::cout << "in 2" << std::endl;
  };
};

int main() {
  X<int> x;
}

When running the program in 2 is printed.

  1. Why is it that the second specialization is chosen over the first since both of them effectively declare a struct X<int, void>. What makes std::enable_if_t<std::is_integral_v<T>> more specialized than a default template type argument as shown in the base template?

  2. Why does the default type argument of the base template have to be the same as the type defined by the partial specialization for the partial specialization to be called and in 2 to be printed. Why does changing to std::enable_if_t<std::is_integral_v<T>, bool> cause the base template in 1 to be called?

max66
  • 65,235
  • 10
  • 71
  • 111
Raees Rajwani
  • 487
  • 5
  • 18
  • You don't need `std::enable_if_t` to ask this question. Simply define the partial specialization as `template struct X` in the first case and the same with `bool` instead of `void` in the second. – Daniel Langr Jul 23 '18 at 12:53

2 Answers2

7

The answers to your questions lie in Template Partial Ordering. This is the mechanism the compiler uses to determine which template is the best fit (be it a function template overload, or in your case, a class template specialization).

In brief, your generic template implementation has 2 parameters T and U, whereas your SFINAE specialization have only the T parameter, while the second is deduced from T. It is therefore more specialized than the general case and in the end, when you refer to X<int, void>, the specialization is chosen.

Now question 2. Suppose we replace the enable_if parameter with bool instead of void. Now our specialization will be X<int, bool> instead of X<int, void>, so when you refer to X<int>, i.e. X<int, void>, it doesn't match the specialization anymore because those are 2 different types.

Rerito
  • 5,886
  • 21
  • 47
1

1) [...] What makes std::enable_if_t> more specialised than a default template type argument as shown in the base template?

So do you know that, if two template match, the more specialized is selected.

Well... the second one is more specialized because if X matches the specialization (so if X is an integral type), it's that matches also the generic version.

But exist couples of types (by example: std::string, void) that matches the generic version and doesn't matches the specialization.

So the specialization is more specialized that the generic version, so is preferred when both template match.

Why does the default type argument of the base template have to be the same as the type defined by the partial specialisation for the partial specialisation to be called and in 2 to be printed. Why does changing to std::enable_if_t, bool> cause the base template in 1 to be called?

You have to understand how works the trick of the default type value.

You have that the generic version that is

template <typename T, typename U = void>
struct X;

so writing X<int> x, you're writing X<int, void> x; and surely matches the generic version.

The specialization is

template <typename T>
struct X< T, std::enable_if_t<std::is_integral_v<T>>>;

Question: X<int, void> matches X< T, std::enable_if_t<std::is_integral_v<T>>> ?

Answer: yes, because int is integral, so std::enable_if_t<std::is_integral_v<T>> is substituted with void.

Suppose now that the generic specialization become

template <typename T>
struct X< T, std::enable_if_t<std::is_integral_v<T>, bool>>

We have that X<int> x is again X<int, void> x and matches again the generic version.

Question: X<int, void> matches also X< T, std::enable_if_t<std::is_integral_v<T>, bool>> ?

Answer: no, because std::enable_if_t<std::is_integral_v<T>, bool> become bool and X<int, void> doesn't matches X<int, bool>

So the generic version is the only one that matches and is selected.

max66
  • 65,235
  • 10
  • 71
  • 111