Edit: I made a simple mistake in using SFINAE. Fixing that resolves the compiler error I mention below. However I'm still curious about why the template parameter can't be deduced in this case.
I wanted to write a C++14 template metaprogram to calculate the greatest common divisor (GCD) of a std::integer_sequence
. After some tinkering I came up with this almost complete example:
template <typename T, T A, T B, T... Ints>
struct GCD<std::integer_sequence<T, A, B, Ints...>> :
GCD<typename std::integer_sequence<T, GCD_pair<T, A, B>::value, Ints...>> {};
template <class T, T A, T B>
struct GCD<std::integer_sequence<T, A, B>> :
GCD_pair<T, A, B> {};
int main() {
using seq = std::integer_sequence<int, 65537, 5, 10>;
cout << GCD<seq>::value << endl;
return 0;
}
I just peel off the first two elements of the integer sequence, and find the GCD of them using the to-be-written GCD_pair
metafunction. Then I apply GCD
to the result of GCD_pair
and the remaining elements.
The "obvious" implementation of GCD_pair
does not compile:
// This does not work:
// type 'T' of template argument '0' depends on a template parameter
template <typename T, T M, T N>
struct GCD_pair : std::integral_constant<T, GCD_pair<T, N, M % N>::value> {};
template <typename T, T M>
struct GCD_pair<T, M, 0> : std::integral_constant<T, M> {};
So I tried another possible implementation using SFINAE:
// This doesn't work either:
// template parameters not deducible in partial specialization
template <typename T, T M, T N, typename = void>
struct GCD_pair : std::integral_constant<T, M> {};
template <typename T, T M, T N, typename std::enable_if<(M % N != 0)>::type>
struct GCD_pair<T, M, N, void> : std::integral_constant<T, GCD_pair<T, N, M % N>::value> {};
Edit: I made an error. See the answer below correcting my use of SFINAE. But I'm still curious about my question:
Why is the template parameter typename std::enable_if<(M % N != 0)>::type
not deducible? Is it not deducible ever in principle, or could parameters such as in this case actually be deduced in practice? In other words, could this be considered a compiler implementation oversight?
For what it's worth, I was able to implement a slightly different version by essentially "hiding" the conditional (M % N != 0
) in a bool
template parameter. However, I think both of the above are reasonable implementations because operator%
and comparison to 0
with operator!=
are perfectly well-defined for all of C++'s integral types.
A non-type template argument provided within a template argument list is an expression whose value can be determined at compile time. Such arguments must be constant expressions, addresses of functions or objects with external linkage, or addresses of static class members.
A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.
A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)
There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.
It should be:
template <typename T, T M, T N>
struct GCD_pair<T, M, N, typename std::enable_if<(M % N != 0)>::type>
: std::integral_constant<T, GCD_pair<T, N, M % N>::value> {};
Because you are using C++14, you can also simplify it by using std::enable_if_t
:
template <typename T, T M, T N>
struct GCD_pair<T, M, N, std::enable_if_t<(M % N != 0)>>
: std::integral_constant<T, GCD_pair<T, N, M % N>::value> {};
In both cases, if the condition (M % N != 0)
applies, both the primary template and the specialization are valid once instantiated, but the specialization is, well, more specialized and thus it is chosen.
On the other side, if the condition doesn't apply, the specialization is silently discarded because of sfinae rules, but the primary template is still valid and thus chosen.
Note also that type
in typename std::enable_if<(M % N != 0)>::type
is void
when the condition is true.
Because of that, you template parameters list would be theoretically:
template <typename T, T M, T N, void>
struct GCD_pair<T, M, N, void>: std::integral_constant<T, GCD_pair<T, N, M % N>::value> {};
However, void
is not allowed as a non-type template parameter.
Finally, from the standard we have that:
A partial specialization matches a given actual template argument list if the template arguments of the partial specialization can be deduced from the actual template argument list
Also:
If the template arguments of a partial specialization cannot be deduced because of the structure of its template-parameter-list and the template-id, the program is ill-formed.
In your case, the fourth parameter of the specialization cannot be deduced for obvious reasons. Even if it was valid (and it is not, because it results in void
as mentioned), what you get is a type or a non-type parameter for which you don't have an actual type or value.
Suppose you have the following specialization:
template <typename T, T M, T N, int>
struct GCD_pair<T, M, N, void> : std::integral_constant<T, GCD_pair<T, N, M % N>::value> {};
How could the compiler deduce the value for the last template parameter?
It cannot and that's more or less your case.
Probably it should rather detect the fact that the parameter list is ill-formed if the condition to std::enable_if
is valid, anyway both are errors and you are getting one of them out of the compilation phase.
What you would benefit from is something like this:
template <typename T, T M, T N, typename = std::enable_if_t<(M % N != 0)>>
struct GCD_pair<T, M, N, void> : std::integral_constant<T, GCD_pair<T, N, M % N>::value> {};
Or this:
template <typename T, T M, T N, std::enable_if_t<(M % N != 0)>* = nullptr>
struct GCD_pair<T, M, N, void> : std::integral_constant<T, GCD_pair<T, N, M % N>::value> {};
Anyway, both of them are invalid for default arguments are not allowed in partial specializations.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With