编辑:我在使用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%
和0
与operator!=
的比较对于所有C ++的整数类型都是完美定义的。
答案 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)>::type
为void
。
因此,模板参数列表理论上是:
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> {};
无论如何,在部分特化中不允许它们都是无效的。