enable_if 的部分模板函数特化:进行默认实现
Partial template function specialization with enable_if: make default implementation
使用 C++11 enable_if
我想为一个函数定义几个专门的实现(例如,基于参数的类型)以及一个默认实现。正确的定义方式是什么?
以下示例无法按预期工作,因为调用了 "generic" 实现,无论类型如何 T
。
#include <iostream>
template<typename T, typename Enable = void>
void dummy(T t)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type>
void dummy(T t)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type>
void dummy(T t)
{
std::cout << "Floating point: " << t << std::endl;
}
int main() {
dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Generic: 5"
}
我的最小示例中的一个解决方案是使用
显式声明 "generic" 实现不针对整数或浮点类型
std::enable_if<!std::is_integral<T>::value && !std::is_floating_point<T>::value>::type
这正是我想要避免的,因为在我的实际用例中有很多专门的实现,我想避免默认实现的很长(容易出错!)的情况。
函数不能部分特化。我假设您想要做的是更喜欢那些包含显式条件的重载?实现这一点的一种方法是在 default
函数的声明中使用可变参数省略号,因为省略号函数在重载解析顺序中具有较低的优先级:
#include <iostream>
template<typename T>
void dummy_impl(T t, ...)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
std::cout << "Floating point: " << t << std::endl;
}
template <class T>
void dummy(T t) {
dummy_impl(t, int{});
}
int main() {
dummy(5);
dummy(5.);
dummy("abc");
}
输出:
Integral: 5
Floating point: 5
Generic: abc
@doublep 在评论中提到的另一种选择是使用结构实现您的功能,然后对其进行部分特化。
你可以引入一个rank
来优先处理你的一些重载:
template <unsigned int N>
struct rank : rank<N - 1> { };
template <>
struct rank<0> { };
然后您可以像这样定义 dummy
重载:
template<typename T>
void dummy(T t, rank<0>)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
std::cout << "Floating point: " << t << std::endl;
}
然后,你可以把调用隐藏在dispatch
:
后面
template <typename T>
void dispatch(T t)
{
return dummy(t, rank<1>{});
}
用法:
int main()
{
dispatch(5); // Print "Integral: 5"
dispatch(5.); // Print "Floating point: 5"
dispatch("hi"); // Print "Generic: hi"
}
解释:
使用 rank
引入 "priority",因为在 X > Y
时需要隐式转换才能将 rank<X>
转换为 rank<Y>
。 dispatch
首先尝试用 rank<1>
调用 dummy
,优先考虑你的约束重载。如果 enable_if
失败,则 rank<1>
隐式转换为 rank<0>
并进入 "fallback" 情况。
奖励: 这是一个使用 if constexpr(...)
.
的 C++17 实现
template<typename T>
void dummy(T t)
{
if constexpr(std::is_integral_v<T>)
{
std::cout << "Integral: " << t << std::endl;
}
else if constexpr(std::is_floating_point_v<T>)
{
std::cout << "Floating point: " << t << std::endl;
}
else
{
std::cout << "Generic: " << t << std::endl;
}
}
我会像这样使用标签调度:
namespace Details
{
namespace SupportedTypes
{
struct Integral {};
struct FloatingPoint {};
struct Generic {};
};
template <typename T, typename = void>
struct GetSupportedType
{
typedef SupportedTypes::Generic Type;
};
template <typename T>
struct GetSupportedType< T, typename std::enable_if< std::is_integral< T >::value >::type >
{
typedef SupportedTypes::Integral Type;
};
template <typename T>
struct GetSupportedType< T, typename std::enable_if< std::is_floating_point< T >::value >::type >
{
typedef SupportedTypes::FloatingPoint Type;
};
template <typename T>
void dummy(T t, SupportedTypes::Generic)
{
std::cout << "Generic: " << t << std::endl;
}
template <typename T>
void dummy(T t, SupportedTypes::Integral)
{
std::cout << "Integral: " << t << std::endl;
}
template <typename T>
void dummy(T t, SupportedTypes::FloatingPoint)
{
std::cout << "Floating point: " << t << std::endl;
}
} // namespace Details
然后像这样隐藏样板代码:
template <typename T>
void dummy(T t)
{
typedef typename Details::GetSupportedType< T >::Type SupportedType;
Details::dummy(t, SupportedType());
}
GetSupportedType
为您提供了一种主要方式来猜测您将要使用的实际类型,这就是您每次添加新类型时想要专门化的类型。
然后您只需通过提供正确的 tag.
的实例来调用正确的 dummy
重载
最后,调用 dummy
:
dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Floating point: 5"
dummy("lol"); // Print "Generic: lol"
使用 C++11 enable_if
我想为一个函数定义几个专门的实现(例如,基于参数的类型)以及一个默认实现。正确的定义方式是什么?
以下示例无法按预期工作,因为调用了 "generic" 实现,无论类型如何 T
。
#include <iostream>
template<typename T, typename Enable = void>
void dummy(T t)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type>
void dummy(T t)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type>
void dummy(T t)
{
std::cout << "Floating point: " << t << std::endl;
}
int main() {
dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Generic: 5"
}
我的最小示例中的一个解决方案是使用
显式声明 "generic" 实现不针对整数或浮点类型std::enable_if<!std::is_integral<T>::value && !std::is_floating_point<T>::value>::type
这正是我想要避免的,因为在我的实际用例中有很多专门的实现,我想避免默认实现的很长(容易出错!)的情况。
函数不能部分特化。我假设您想要做的是更喜欢那些包含显式条件的重载?实现这一点的一种方法是在 default
函数的声明中使用可变参数省略号,因为省略号函数在重载解析顺序中具有较低的优先级:
#include <iostream>
template<typename T>
void dummy_impl(T t, ...)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
std::cout << "Floating point: " << t << std::endl;
}
template <class T>
void dummy(T t) {
dummy_impl(t, int{});
}
int main() {
dummy(5);
dummy(5.);
dummy("abc");
}
输出:
Integral: 5
Floating point: 5
Generic: abc
@doublep 在评论中提到的另一种选择是使用结构实现您的功能,然后对其进行部分特化。
你可以引入一个rank
来优先处理你的一些重载:
template <unsigned int N>
struct rank : rank<N - 1> { };
template <>
struct rank<0> { };
然后您可以像这样定义 dummy
重载:
template<typename T>
void dummy(T t, rank<0>)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
std::cout << "Floating point: " << t << std::endl;
}
然后,你可以把调用隐藏在dispatch
:
template <typename T>
void dispatch(T t)
{
return dummy(t, rank<1>{});
}
用法:
int main()
{
dispatch(5); // Print "Integral: 5"
dispatch(5.); // Print "Floating point: 5"
dispatch("hi"); // Print "Generic: hi"
}
解释:
使用 rank
引入 "priority",因为在 X > Y
时需要隐式转换才能将 rank<X>
转换为 rank<Y>
。 dispatch
首先尝试用 rank<1>
调用 dummy
,优先考虑你的约束重载。如果 enable_if
失败,则 rank<1>
隐式转换为 rank<0>
并进入 "fallback" 情况。
奖励: 这是一个使用 if constexpr(...)
.
template<typename T>
void dummy(T t)
{
if constexpr(std::is_integral_v<T>)
{
std::cout << "Integral: " << t << std::endl;
}
else if constexpr(std::is_floating_point_v<T>)
{
std::cout << "Floating point: " << t << std::endl;
}
else
{
std::cout << "Generic: " << t << std::endl;
}
}
我会像这样使用标签调度:
namespace Details
{
namespace SupportedTypes
{
struct Integral {};
struct FloatingPoint {};
struct Generic {};
};
template <typename T, typename = void>
struct GetSupportedType
{
typedef SupportedTypes::Generic Type;
};
template <typename T>
struct GetSupportedType< T, typename std::enable_if< std::is_integral< T >::value >::type >
{
typedef SupportedTypes::Integral Type;
};
template <typename T>
struct GetSupportedType< T, typename std::enable_if< std::is_floating_point< T >::value >::type >
{
typedef SupportedTypes::FloatingPoint Type;
};
template <typename T>
void dummy(T t, SupportedTypes::Generic)
{
std::cout << "Generic: " << t << std::endl;
}
template <typename T>
void dummy(T t, SupportedTypes::Integral)
{
std::cout << "Integral: " << t << std::endl;
}
template <typename T>
void dummy(T t, SupportedTypes::FloatingPoint)
{
std::cout << "Floating point: " << t << std::endl;
}
} // namespace Details
然后像这样隐藏样板代码:
template <typename T>
void dummy(T t)
{
typedef typename Details::GetSupportedType< T >::Type SupportedType;
Details::dummy(t, SupportedType());
}
GetSupportedType
为您提供了一种主要方式来猜测您将要使用的实际类型,这就是您每次添加新类型时想要专门化的类型。
然后您只需通过提供正确的 tag.
的实例来调用正确的dummy
重载
最后,调用 dummy
:
dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Floating point: 5"
dummy("lol"); // Print "Generic: lol"