Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this template parameter not deducible?

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.

like image 234
JohnDuck Avatar asked Sep 30 '16 06:09

JohnDuck


People also ask

What are non-type parameters for templates?

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.

What is template type parameter?

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.

Can a template be a template parameter?

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.)

How will you restrict the template for a specific datatype?

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.


1 Answers

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.

like image 149
skypjack Avatar answered Oct 08 '22 19:10

skypjack