SFINAE 函数的 "catch-all"?
A "catch-all" for SFINAE functions?
我正在尝试制作一个类型比较函数,为不同的类型做一些自定义比较。
#include <type_traits>
template <typename T>
bool typedCompare(const T& lhs, const T& rhs)
{
return lhs == rhs; // default case, use ==
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
typedCompare(const T& lhs, const T& rhs)
{
return (lhs - rhs) < 1e-10;
}
int main()
{
typedCompare(1, 1);
typedCompare(1.0, 1.0);
return 0;
}
这里我有一个针对double
的特殊版本,比较小的差异(请忽略我没有使用std::abs()
)。我还有其他一些自定义类型需要进行一些特殊比较,但出于某种原因我无法更改它们的 ==
运算符。
此外,我还想要一个使用 ==
运算符的 "catch-all" 风格的函数。我的问题是,当尝试编译此代码片段时,编译器抱怨 typedCompare(1.0, 1.0)
不明确,它可以选择提供的两个函数之一。
为什么?我该如何解决这个问题?
谢谢。
Why?
简而言之,您错误地使用了 SFINAE,因此当您为双打调用 typedCompare
时,两个函数模板均有效。
And how could I resolve this issue?
在这种特殊情况下,修复 SFINAE 使其正常工作:
template <typename T>
typename std::enable_if<!std::is_floating_point<T>::value, bool>::type
typedCompare(const T& lhs, const T& rhs)
{
return lhs == rhs; // default case, use ==
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
typedCompare(const T& lhs, const T& rhs)
{
return (lhs - rhs) < 1e-10;
}
请注意,就许多类型的定制而言,此解决方案不是很好。另一种方法是使用标签调度:
struct floating_point_tag {};
// other tags
template <typename T>
bool typedCompare(const T& lhs, const T& rhs, floating_point_tag) {
return (lhs - rhs) < 1e-10;
}
// implementations for other tags
template <typename T>
bool typedCompare(const T& lhs, const T& rhs) {
if (std::is_floating_point<T>::value) {
return typedCompare(lhs, rhs, floating_point_tag{});
}
// other checks here
return lhs == rhs;
}
最后,对于 C++17,您可以使用 if constexpr
:
template <typename T>
bool typedCompare(const T& lhs, const T& rhs) {
if constexpr (std::is_floating_point<T>::value) {
return (lhs - rhs) < 1e-10;
} else { // add other if-else here
return lhs == rhs;
}
}
你问题中的代码存在的问题是,当启用 SFINAE 浮点数 typedCompare()
时,会与通用版本发生冲突,因为编译器无法优先选择一个版本。
为了解决这个问题,我建议您使用另一种方法,基于模板偏特化(仅适用于结构和 类,因此需要一个助手 struct
)
如果你定义一个helperstruct
如下
template <typename T, typename = void>
struct typedCompareHelper
{
static constexpr bool func (T const & lhs, T const & rhs)
{ return lhs == rhs; } // default case, use ==
};
template <typename T>
struct typedCompareHelper<T,
typename std::enable_if<std::is_floating_point<T>::value>::type>
{
static constexpr bool func (T const & lhs, T const & rhs)
{ return (lhs - rhs) < 1e-10; }
};
您避免了歧义问题,因为 typedCompareHelper
专业化比通用化更专业化。
您可以简单地为不同的特殊情况添加更多专业化,只注意避免冲突(同一级别的不同专业化适用于同一类型)。
你的typedCompare()
变得简单
template <typename T>
bool typedCompare (T const & lhs, T const & rhs)
{ return typedCompareHelper<T>::func(lhs, rhs); }
我喜欢@max66 的带有辅助模板的解决方案和@Edgar Rokyan 的多个解决方案。
这是另一种方法,可以使用辅助模板函数来满足您的需求。
#include <type_traits>
#include <iostream>
#include <string>
// elipsis version is at the bottom of the overload resolution priority.
// it will only be used if nothing else matches the overload.
void typeCompare_specialized(...)
{
std::cout << "SHOULD NEVER BE CALLED!!!\n";
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
typeCompare_specialized(const T& lhs, const T& rhs)
{
std::cout << "floating-point version\n";
return (lhs - rhs) < 1e-10;
}
template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
typeCompare_specialized(const T& lhs, const T& rhs)
{
std::cout << "integral version\n";
return lhs == rhs;
}
template <typename T>
auto typedCompare(const T& lhs, const T& rhs)
-> typename std::enable_if<std::is_same<bool,decltype(typeCompare_specialized(lhs, rhs))>::value,bool>::type
{
return typeCompare_specialized(lhs, rhs);
}
template <typename T>
auto typedCompare(const T& lhs, const T& rhs)
-> typename std::enable_if<!std::is_same<bool,decltype(typeCompare_specialized(lhs, rhs))>::value,bool>::type
{
std::cout << "catch-all version\n";
return lhs == rhs;
}
int main()
{
typedCompare(1, 1);
typedCompare(1.0, 1.0);
typedCompare(std::string("hello"), std::string("there"));
return 0;
}
运行 上述程序将产生以下输出:
integral version
floating-point version
catch-all version
同样,我更愿意使用前面提到的答案之一。为了完整起见,我将这种可能性包括在内。
我还想补充一点,你应该确保你的 typeCompare_specialized()
模板版本不应该有任何重叠,否则你可能会得到一个编译器错误,声明有多个候选重载要使用。
我正在尝试制作一个类型比较函数,为不同的类型做一些自定义比较。
#include <type_traits>
template <typename T>
bool typedCompare(const T& lhs, const T& rhs)
{
return lhs == rhs; // default case, use ==
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
typedCompare(const T& lhs, const T& rhs)
{
return (lhs - rhs) < 1e-10;
}
int main()
{
typedCompare(1, 1);
typedCompare(1.0, 1.0);
return 0;
}
这里我有一个针对double
的特殊版本,比较小的差异(请忽略我没有使用std::abs()
)。我还有其他一些自定义类型需要进行一些特殊比较,但出于某种原因我无法更改它们的 ==
运算符。
此外,我还想要一个使用 ==
运算符的 "catch-all" 风格的函数。我的问题是,当尝试编译此代码片段时,编译器抱怨 typedCompare(1.0, 1.0)
不明确,它可以选择提供的两个函数之一。
为什么?我该如何解决这个问题?
谢谢。
Why?
简而言之,您错误地使用了 SFINAE,因此当您为双打调用 typedCompare
时,两个函数模板均有效。
And how could I resolve this issue?
在这种特殊情况下,修复 SFINAE 使其正常工作:
template <typename T>
typename std::enable_if<!std::is_floating_point<T>::value, bool>::type
typedCompare(const T& lhs, const T& rhs)
{
return lhs == rhs; // default case, use ==
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
typedCompare(const T& lhs, const T& rhs)
{
return (lhs - rhs) < 1e-10;
}
请注意,就许多类型的定制而言,此解决方案不是很好。另一种方法是使用标签调度:
struct floating_point_tag {};
// other tags
template <typename T>
bool typedCompare(const T& lhs, const T& rhs, floating_point_tag) {
return (lhs - rhs) < 1e-10;
}
// implementations for other tags
template <typename T>
bool typedCompare(const T& lhs, const T& rhs) {
if (std::is_floating_point<T>::value) {
return typedCompare(lhs, rhs, floating_point_tag{});
}
// other checks here
return lhs == rhs;
}
最后,对于 C++17,您可以使用 if constexpr
:
template <typename T>
bool typedCompare(const T& lhs, const T& rhs) {
if constexpr (std::is_floating_point<T>::value) {
return (lhs - rhs) < 1e-10;
} else { // add other if-else here
return lhs == rhs;
}
}
你问题中的代码存在的问题是,当启用 SFINAE 浮点数 typedCompare()
时,会与通用版本发生冲突,因为编译器无法优先选择一个版本。
为了解决这个问题,我建议您使用另一种方法,基于模板偏特化(仅适用于结构和 类,因此需要一个助手 struct
)
如果你定义一个helperstruct
如下
template <typename T, typename = void>
struct typedCompareHelper
{
static constexpr bool func (T const & lhs, T const & rhs)
{ return lhs == rhs; } // default case, use ==
};
template <typename T>
struct typedCompareHelper<T,
typename std::enable_if<std::is_floating_point<T>::value>::type>
{
static constexpr bool func (T const & lhs, T const & rhs)
{ return (lhs - rhs) < 1e-10; }
};
您避免了歧义问题,因为 typedCompareHelper
专业化比通用化更专业化。
您可以简单地为不同的特殊情况添加更多专业化,只注意避免冲突(同一级别的不同专业化适用于同一类型)。
你的typedCompare()
变得简单
template <typename T>
bool typedCompare (T const & lhs, T const & rhs)
{ return typedCompareHelper<T>::func(lhs, rhs); }
我喜欢@max66 的带有辅助模板的解决方案和@Edgar Rokyan 的多个解决方案。
这是另一种方法,可以使用辅助模板函数来满足您的需求。
#include <type_traits>
#include <iostream>
#include <string>
// elipsis version is at the bottom of the overload resolution priority.
// it will only be used if nothing else matches the overload.
void typeCompare_specialized(...)
{
std::cout << "SHOULD NEVER BE CALLED!!!\n";
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
typeCompare_specialized(const T& lhs, const T& rhs)
{
std::cout << "floating-point version\n";
return (lhs - rhs) < 1e-10;
}
template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
typeCompare_specialized(const T& lhs, const T& rhs)
{
std::cout << "integral version\n";
return lhs == rhs;
}
template <typename T>
auto typedCompare(const T& lhs, const T& rhs)
-> typename std::enable_if<std::is_same<bool,decltype(typeCompare_specialized(lhs, rhs))>::value,bool>::type
{
return typeCompare_specialized(lhs, rhs);
}
template <typename T>
auto typedCompare(const T& lhs, const T& rhs)
-> typename std::enable_if<!std::is_same<bool,decltype(typeCompare_specialized(lhs, rhs))>::value,bool>::type
{
std::cout << "catch-all version\n";
return lhs == rhs;
}
int main()
{
typedCompare(1, 1);
typedCompare(1.0, 1.0);
typedCompare(std::string("hello"), std::string("there"));
return 0;
}
运行 上述程序将产生以下输出:
integral version
floating-point version
catch-all version
同样,我更愿意使用前面提到的答案之一。为了完整起见,我将这种可能性包括在内。
我还想补充一点,你应该确保你的 typeCompare_specialized()
模板版本不应该有任何重叠,否则你可能会得到一个编译器错误,声明有多个候选重载要使用。