【问题标题】:SFINAE to check if std::less will workSFINAE 检查 std::less 是否有效
【发布时间】:2018-02-23 17:51:32
【问题描述】:

在我的代码中,如果一个对象小于另一个对象,我希望一个操作先于另一个操作发生。但是,如果类型不可比较,则顺序无关紧要。为此,我尝试使用 SFINAE:

template<typename T, typename = decltype(std::declval<std::less<T>>()(std::declval<T>(), std::declval<T>()))> 
bool ComparableAndLessThan(const T& lhs, const T& rhs) {
    return std::less<T>()(lhs, rhs);
}
bool ComparableAndLessThan(...) {
    return false;
}

struct foo {};
int main()
{
    foo a,b;
    if (ComparableAndLessThan(a, b)) {
        std::cout << "a first" << std::endl;
    } else {
        std::cout << "b first" << std::endl;
    }
}

但是这不起作用。如果我创建一个对象而不给它一个 operator

error C2678: binary '<': no operator found which takes a left-hand operand of type 'const foo' (or there is no acceptable conversion)
note: could be 'bool std::operator <(const std::error_condition &,const std::error_condition &) noexcept'
note: or       'bool std::operator <(const std::error_code &,const std::error_code &) noexcept'
note: while trying to match the argument list '(const foo, const foo)'
note: while compiling class template member function 'bool std::less<T>::operator ()(const _Ty &,const _Ty &) const'
       with
       [
           T=foo,
           _Ty=foo
       ]

我假设因为声明存在,SFINAE 不会将此视为错误,即使实现会导致错误。有什么方法可以检查 std::less 是否可以用于模板类型?

【问题讨论】:

标签: c++ sfinae


【解决方案1】:

代码有两个问题。首先很容易解决:默认模板参数不是重载解析的一部分,不应该用于 SFINAE 类型的解析。有一个规范的修复方法,你创建一个your_decltype* 类型的非类型模板参数并默认为nullptr。

第二个问题更难。即使进行了上述修复,SFINAE 也不起作用,因为没有替代错误。 std::less&lt;T&gt; 是为每个T定义的,只是在调用operator&lt; 时出现编译错误。解决它的一种方法是直接使用 operator&lt; 来表示您的类型:

template<typename T, 
     decltype(std::declval<T>() < std::declval<T>())* = nullptr> 
bool ComparableAndLessThan(const T& lhs, const T& rhs) {
    return std::less<T>()(lhs, rhs);
}
bool ComparableAndLessThan(...) {
    return false;
}

但它可能不是你想要的。我不知道如何使它与定义非常广泛的 std::less 一起工作。

【讨论】:

  • 好的。所以我可以检查operator&lt;,但是没有办法检查是否有std::less的专门化。我想我会这样做。然后允许他们传递一个默认为std::less&lt;void&gt; 的比较器。如果我发现它等于std::less&lt;void&gt;,我会检查operator&lt;。如果不相等,我会假设他们的比较器工作,如果他们得到错误,那是他们的错。
  • 对不起,但是...关于“默认模板参数不是重载解析的一部分,不应用于 SFINAE 类型解析”部分...您确定吗?您的意思是您不能将template&lt;typename T, typename = decltype( std::declval&lt;T&gt;() &lt; std::declval&lt;T&gt;())&gt; 用于 SFINAE 目的?
  • @max66 可以,但它不是函数签名的一部分,因此不参与重载决议。更多详情:en.cppreference.com/w/cpp/types/enable_if
  • @JonathanGawrych 我不是说没有办法,但我不知道。
  • @SergeyA - 所以当你写“不应该用于 SFINAE 类型的分辨率”时,你的意思是“你可以将它用于 SFINAE 但很危险,所以最好避免它”?跨度>
【解决方案2】:

我想用一个简单的例子来说明问题所在。

首先SergeyA是正确的,你需要将它设为非类型模板参数默认nullptr。

现在来看看为什么它不起作用。

因为std::less 中的operator() 对SFINAE 不友好。让我告诉你我的意思:

template <class T> struct Not_working
{
    // not SFINAE friendly
    auto operator()(T a, T b) { return a < b; }
};

这就是std::less::operator() 的样子。方法operator() 是无条件定义的。即使T 是否有&lt;,operator() 也存在并且有一个有效声明。错误在函数体中。

当您在 SFINAE 上下文中使用 Not_working::operator() 时,它的主体不是直接上下文,因此对于 SFINAE,Not_working::operator() 是有效的而不是失败的。因此,为重载集保留了特化,然后我们有一个错误。

为了在 SFINAE 中可用,operator() 在正文中存在错误时不得参与重载解决方案。换句话说,它本身必须是 SFINAEd:

template <class T> struct Working
{
    // SFINAE friendly
    template <class TT = T, decltype(TT{} < TT{})* = nullptr>
    auto operator()(T a, T b) { return a < b; }
};

现在,当 Working::operator() 在 SFINAE 上下文中使用时,例如在您的 ComparableAndLessThan 中,我们将出现不是错误的替换失败,因此 SFINAE 将按预期工作。

在我看来,不让这些成员对 SFINAE 友好是对委员会的疏忽,但可能有一些因素我没有考虑。

【讨论】:

    【解决方案3】:

    你可以这样重写。

    template<typename T, typename F = typename std::enable_if<std::is_same<decltype(std::declval<T>() < std::declval<T>()), bool>::value, bool>::type> 
    F ComparableAndLessThan(const T& lhs, const T& rhs) {
        return std::less<T>()(lhs, rhs);
    }
    bool ComparableAndLessThan(...) {
        return false;
    }
    

    将您的检查改为查看 decltype(std::declval&lt;T&gt;() &lt; std::declval&lt;T&gt;()) 是否产生布尔值。

    【讨论】:

      【解决方案4】:

      SFINAE 仅适用于模板的直接上下文。在您的情况下,错误发生在 std::less::operator() 但是,您可以先测试 operator this。

      【讨论】:

      • 唯一的问题是 std::less 仍然是一个完整的类型,即使它没有专门化:/
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-07
      • 2015-04-25
      • 2014-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-11
      相关资源
      最近更新 更多