为什么这个模板参数不可推导?

Why is this template parameter not deducible?

编辑:我在使用 SFINAE 时犯了一个简单的错误。修复解决了我在下面提到的编译器错误。但是我还是很好奇为什么在这种情况下不能推导出模板参数。

我想编写一个 C++14 模板元程序来计算 std::integer_sequence 的最大公约数 (GCD)。经过一些修补,我想出了这个 almost 完整的例子:

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;
}

我只是剥离整数序列的前两个元素,并使用待写的GCD_pair元函数找到它们的GCD。然后我将 GCD 应用于 GCD_pair 和其余元素的结果。

GCD_pair 的 "obvious" 实现不编译:

// 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> {};

所以我尝试了另一种可能的使用 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> {};

编辑:我犯了一个错误。请参阅下面更正我对 SFINAE 的使用的答案。但是我还是很好奇我的问题:

为什么模板参数typename std::enable_if<(M % N != 0)>::type不可推导?是不是原则上不可推导ever,或者这样的参数可以在这种情况下 actually 可以在实践中推导出来吗?换句话说,这可以被视为编译器实现疏忽吗?

对于它的价值,我能够通过 "hiding" bool 模板参数中的条件 (M % N != 0) 来实现 a slightly different version。但是,我认为以上两个都是合理的实现,因为 operator% 以及与 0operator!= 的比较对于所有 C++ 都是完美定义的整数类型。

应该是:

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> {};

因为你用的是C++14,你也可以用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> {};

在这两种情况下,如果条件 (M % N != 0) 适用,则主模板和特化在实例化后均有效,但特化更特化,因此被选中。
另一方面,如果条件不适用,由于 sfinae 规则,特化被静默丢弃,但主模板仍然有效,因此被选中。

另请注意,当条件为真时,typename std::enable_if<(M % N != 0)>::type 中的 typevoid
因此,您的模板参数列表理论上是:

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> {};

但是,void 不允许作为非类型模板参数。

最后,根据标准我们有:

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

还有:

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.

在您的情况下,由于显而易见的原因无法推导出特化的第四个参数。即使它是有效的(它不是,因为它导致 void 如前所述),你得到的是一个类型或一个你没有实际类型或值的非类型参数。
假设您有以下专业:

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> {};

编译器如何推导出最后一个模板参数的值?
它不能,这或多或少是你的情况。 如果 std::enable_if 的条件有效,它可能更应该检测参数列表格式错误的事实,无论如何都是错误,并且您正在将其中之一从编译阶段中取出。

你会从中受益的是这样的:

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> {};

或者这个:

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> {};

无论如何,它们都无效,因为默认参数在偏特化中是不允许的。