Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE to check if std::less will work

Tags:

c++

sfinae

In my code I would like to have one operation happen before another if one object is less than another. However, if the type isn't comparable it doesn't matter the order. To achieve this, I've tried to use 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;
    }
}

However this did not work. If I create an object without giving it a operator< or a std::less specialization, I get this error.

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
       ]

I'm assuming because the declaration exists, SFINAE doesn't see this as an error, even though the implementation causes an error. Is there any way to check if std::less can be used on a templated type?

like image 571
Jonathan Gawrych Avatar asked Jan 28 '23 14:01

Jonathan Gawrych


1 Answers

The code has two issues. First is simple to fix: default template arguments are not part of overload resolution, and should not be used for SFINAE-type resolution. There is a canonical fix to it, your make a non-type template parameter of type your_decltype* and default it nullptr.

The second issue is harder. Even with fix above, SFINAE doesn't work because there is no substitute error. std::less<T> is defined for every T, it is just there is a compilation error when operator< is called. One way to solve it would be to directly use operator< for your types:

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;
}

But it's likely not want you want. I do not know how to make it work with very wildly defined std::less.

like image 156
SergeyA Avatar answered Feb 05 '23 05:02

SergeyA