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.
Why is it that the second specialization is chosen over the first since both of them effectively declare a
struct X<int, void>
. What makesstd::enable_if_t<std::is_integral_v<T>>
more specialized than a default template type argument as shown in the base template?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 tostd::enable_if_t<std::is_integral_v<T>, bool>
cause the base templatein 1
to be called?
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.
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.
来源:https://stackoverflow.com/questions/51479015/template-specialisation-with-default-argument