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() 模板版本不应该有任何重叠,否则你可能会得到一个编译器错误,声明有多个候选重载要使用。