std::enable_if 和 std::is_arithmetic 作为模板参数的问题
Troubles with std::enable_if and std::is_arithmetic as template parameter
我正在尝试实现一个 OutputArchive
模板 class,它有一个模板函数 processImpl()
。看起来像这样:
template<typename ArchiveType>
class OutputArchive {
...
template<typename Type, typename std::enable_if_t<std::is_arithmetic_v<Type>>> inline
ArchiveType& processImpl(Type&& type) {
// Implementation
}
template<typename Type, typename = void> inline
ArchiveType& processImpl(Type&& type) {
// Implementation
}
}
这里的想法是,如果我将 char
、int
、float
等传递给我的 processImpl()
函数,则应使用第一个重载;然而,事实并非如此。第二个过载似乎总是被使用,我完全不知道我可能做错了什么。我想这确实与我使用 std::enable_if
的方式有关
因此,为了使其正常工作,您应该在 2 个案例中使用 std::enable_if。
我将展示 return 类型的示例,但使用模板参数也可以。
template<typename Type> inline
typename std::enable_if_t<std::is_arithmetic_v<Type>, ArchiveType&> processImpl(Type&& type) {
// Implementation
}
template<typename Type> inline
typename std::enable_if_t<!std::is_arithmetic_v<Type>, ArchiveType&> processImpl(Type&& type) {
// Implementation
}
注意第二种情况的否定。
但是从 C++17 开始,更好的方法是使用 constexpr:
ArchiveType& processImpl(Type&& type) {
if constexpr(std::is_arithmetic_v<type>) {
// implementation
} else {
// implementation
}
}
这应该可以解决问题
template<typename ArchiveType>
class OutputArchive {
...
template<typename Type>
inline
typename std::enable_if_t<std::is_arithmetic_v<Type>, ArchiveType&>
processImpl(Type type) {
// Implementation
}
template<typename Type>
inline
typename std::enable_if_t<!std::is_arithmetic_v<Type>, ArchiveType&>
processImpl(Type&& type) {
// Implementation
}
};
你的代码有问题。
排名不分先后
1) 不是错误(我想)但是...使用 typename std::enable_if<...>::type
或者,从 C++14 开始,std::enable_if_t<...>
;无需在 std::enable_if_t
之前使用 typename
2) 如果要在参数类型列表中使用std::enable_if
,这个不行
template <typename T, std::enable_if_t<(test with T)>>
因为,如果 T
的测试为真,则变为
template <typename T, void>
作为模板函数的签名没有意义。
您可以 SFINAE enable/disable return 值(参见 Igor 或 Marek R 的答案)或者您可以写成
template <typename T, std::enable_if_t<(test with T)> * = nullptr>
变成
template <typename T, void * = nullptr>
有意义,作为签名,并且有效
3) 正如评论中指出的那样,你应该使用 std::remove_reference
,所以
template <typename Type,
std::enable_if_t<std::is_arithmetic_v<
std::remove_reference_t<Type>>> * = nullptr> inline
ArchiveType & processImpl (Type && type)
现在这个函数应该拦截算术值但是...
4) 前面的 processImpl()
,对于算术值,与另一个 processImpl()
冲突,因为对于算术值,两个版本都匹配,编译器不能 select一个接一个。
我可以提出两个解决方案
(a) 通过 SFINAE 禁用算术案例中的第二个版本;我的意思是,写第二个如下
template <typename Type,
std::enable_if_t<false == std::is_arithmetic_v<
std::remove_reference_t<Type>>> * = nullptr> inline
ArchiveType & processImpl (Type && type)
(b) 通过一个中间函数,该函数发送一个额外的 int
值并接收算术版本中的 int
和通用版本中的 long
;我的意思是
template <typename Type,
std::enable_if_t<std::is_arithmetic_v<
std::remove_reference_t<Type>>> * = nullptr> inline
ArchiveType & processImpl (Type && type, int)
{ /* ... */ }
template <typename Type>
ArchiveType & processImpl (Type && type, long)
{ /* ... */ }
template <typename Type>
ArchiveType & processImpl (Type && type)
{ return processImpl(type, 0); }
通过这种方式,算术版本准确接收 int
,优于通用版本(启用时);否则使用通用版本。
以下是基于 (b) 解决方案的完整工作 C++14 示例
#include <iostream>
#include <type_traits>
template <typename ArchiveType>
struct OutputArchive
{
ArchiveType value {};
template <typename Type,
std::enable_if_t<std::is_arithmetic_v<
std::remove_reference_t<Type>>> * = nullptr> inline
ArchiveType & processImpl (Type && type, int)
{
std::cout << "--- processImpl aritmetic: " << type << std::endl;
return value;
}
template <typename Type>
ArchiveType & processImpl (Type && type, long)
{
std::cout << "--- processImpl generic: " << type << std::endl;
return value;
}
template <typename Type>
ArchiveType & processImpl (Type && type)
{ return processImpl(type, 0); }
};
int main()
{
OutputArchive<int> oa;
long l{2l};
oa.processImpl(l);
oa.processImpl(3);
oa.processImpl("abc");
}
我正在尝试实现一个 OutputArchive
模板 class,它有一个模板函数 processImpl()
。看起来像这样:
template<typename ArchiveType>
class OutputArchive {
...
template<typename Type, typename std::enable_if_t<std::is_arithmetic_v<Type>>> inline
ArchiveType& processImpl(Type&& type) {
// Implementation
}
template<typename Type, typename = void> inline
ArchiveType& processImpl(Type&& type) {
// Implementation
}
}
这里的想法是,如果我将 char
、int
、float
等传递给我的 processImpl()
函数,则应使用第一个重载;然而,事实并非如此。第二个过载似乎总是被使用,我完全不知道我可能做错了什么。我想这确实与我使用 std::enable_if
的方式有关
因此,为了使其正常工作,您应该在 2 个案例中使用 std::enable_if。 我将展示 return 类型的示例,但使用模板参数也可以。
template<typename Type> inline
typename std::enable_if_t<std::is_arithmetic_v<Type>, ArchiveType&> processImpl(Type&& type) {
// Implementation
}
template<typename Type> inline
typename std::enable_if_t<!std::is_arithmetic_v<Type>, ArchiveType&> processImpl(Type&& type) {
// Implementation
}
注意第二种情况的否定。
但是从 C++17 开始,更好的方法是使用 constexpr:
ArchiveType& processImpl(Type&& type) {
if constexpr(std::is_arithmetic_v<type>) {
// implementation
} else {
// implementation
}
}
这应该可以解决问题
template<typename ArchiveType>
class OutputArchive {
...
template<typename Type>
inline
typename std::enable_if_t<std::is_arithmetic_v<Type>, ArchiveType&>
processImpl(Type type) {
// Implementation
}
template<typename Type>
inline
typename std::enable_if_t<!std::is_arithmetic_v<Type>, ArchiveType&>
processImpl(Type&& type) {
// Implementation
}
};
你的代码有问题。
排名不分先后
1) 不是错误(我想)但是...使用 typename std::enable_if<...>::type
或者,从 C++14 开始,std::enable_if_t<...>
;无需在 std::enable_if_t
typename
2) 如果要在参数类型列表中使用std::enable_if
,这个不行
template <typename T, std::enable_if_t<(test with T)>>
因为,如果 T
的测试为真,则变为
template <typename T, void>
作为模板函数的签名没有意义。
您可以 SFINAE enable/disable return 值(参见 Igor 或 Marek R 的答案)或者您可以写成
template <typename T, std::enable_if_t<(test with T)> * = nullptr>
变成
template <typename T, void * = nullptr>
有意义,作为签名,并且有效
3) 正如评论中指出的那样,你应该使用 std::remove_reference
,所以
template <typename Type,
std::enable_if_t<std::is_arithmetic_v<
std::remove_reference_t<Type>>> * = nullptr> inline
ArchiveType & processImpl (Type && type)
现在这个函数应该拦截算术值但是...
4) 前面的 processImpl()
,对于算术值,与另一个 processImpl()
冲突,因为对于算术值,两个版本都匹配,编译器不能 select一个接一个。
我可以提出两个解决方案
(a) 通过 SFINAE 禁用算术案例中的第二个版本;我的意思是,写第二个如下
template <typename Type,
std::enable_if_t<false == std::is_arithmetic_v<
std::remove_reference_t<Type>>> * = nullptr> inline
ArchiveType & processImpl (Type && type)
(b) 通过一个中间函数,该函数发送一个额外的 int
值并接收算术版本中的 int
和通用版本中的 long
;我的意思是
template <typename Type,
std::enable_if_t<std::is_arithmetic_v<
std::remove_reference_t<Type>>> * = nullptr> inline
ArchiveType & processImpl (Type && type, int)
{ /* ... */ }
template <typename Type>
ArchiveType & processImpl (Type && type, long)
{ /* ... */ }
template <typename Type>
ArchiveType & processImpl (Type && type)
{ return processImpl(type, 0); }
通过这种方式,算术版本准确接收 int
,优于通用版本(启用时);否则使用通用版本。
以下是基于 (b) 解决方案的完整工作 C++14 示例
#include <iostream>
#include <type_traits>
template <typename ArchiveType>
struct OutputArchive
{
ArchiveType value {};
template <typename Type,
std::enable_if_t<std::is_arithmetic_v<
std::remove_reference_t<Type>>> * = nullptr> inline
ArchiveType & processImpl (Type && type, int)
{
std::cout << "--- processImpl aritmetic: " << type << std::endl;
return value;
}
template <typename Type>
ArchiveType & processImpl (Type && type, long)
{
std::cout << "--- processImpl generic: " << type << std::endl;
return value;
}
template <typename Type>
ArchiveType & processImpl (Type && type)
{ return processImpl(type, 0); }
};
int main()
{
OutputArchive<int> oa;
long l{2l};
oa.processImpl(l);
oa.processImpl(3);
oa.processImpl("abc");
}