为什么这个模板参数不可推导?
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%
以及与 0
和 operator!=
的比较对于所有 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
中的 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
不允许作为非类型模板参数。
最后,根据标准我们有:
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> {};
无论如何,它们都无效,因为默认参数在偏特化中是不允许的。
编辑:我在使用 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%
以及与 0
和 operator!=
的比较对于所有 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
中的 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
不允许作为非类型模板参数。
最后,根据标准我们有:
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> {};
无论如何,它们都无效,因为默认参数在偏特化中是不允许的。