为什么此模板参数无法推导?

时间:2016-09-30 06:05:25

标签: c++ templates compiler-errors c++14 template-meta-programming

编辑:我在使用SFINAE时犯了一个简单的错误。修复解决了我在下面提到的编译器错误。但是我仍然对为什么在这种情况下无法推断模板参数感到好奇。

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

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的“明显”实现无法编译:

// 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无法推导?原则上它是否永远 <或者> <或者> 在实践中推断?换句话说,这可能被视为编译器实现的疏忽吗?

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

1 个答案:

答案 0 :(得分:5)

应该是:

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规则而静默地丢弃特化,但主模板仍然有效并因此被选中。

当条件为真时,请注意type中的typename std::enable_if<(M % N != 0)>::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不允许作为非类型模板参数。

最后,从标准我们得到:

  

如果可以从实际模板参数列表推导出部分特化的模板参数,则部分特化匹配给定的实际模板参数列表

此外:

  

如果由于其template-parameter-list和template-id的结构而无法推断出部分特化的模板参数,则该程序格式不正确。

在您的情况下,由于显而易见的原因,无法推断出专业化的第四个参数。即使它是有效的(并且它不是,因为它导致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> {};

无论如何,在部分特化中不允许它们都是无效的。