Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check whether an operator is overloaded in C++ [closed]

I'm writing a template function for shuffling, and I want to check whether the 'less than' operator is overloaded on an arbitrary data structure before trying to use it. It this possible?

like image 208
Viktor Avatar asked Jan 02 '23 15:01

Viktor


1 Answers

We can use the Detection Idiom to test whether T < T is well formed at compile time.

For readability, I'm using experimental::is_detected, but you can roll your own in C++11 with the voider pattern.

First, a class it works for which a < is well formed:

struct Has_Less_Than{
    int value;  
};

bool operator < (const Has_Less_Than& lhs, const Has_Less_Than& rhs) {return lhs.value < rhs.value; }

And then one where it isn't:

struct Doesnt_Have_Less_Than{
    int value;
};
// no operator < defined

Now, for the detection idiom part: we try to get the type of the result of a "theoretical" comparison, and then ask is_detected:

template<class T>
using less_than_t = decltype(std::declval<T>() < std::declval<T>());

template<class T>
constexpr bool has_less_than = is_detected<less_than_t, T>::value;


int main()
{
    std::cout << std::boolalpha << has_less_than<Has_Less_Than> << std::endl; // true
    std::cout << std::boolalpha << has_less_than<Doesnt_Have_Less_Than> << std::endl; // false
}

Live Demo

If you have C++17 available, you can take advantage of constexpr if for your test:

if constexpr(has_less_than<Has_Less_Than>){
    // do something with <
}
else{
    // do something else
    
}

It works because constexpr if is evaluated at compile-time, and the compiler will only compile the branch that was taken.


If you don't have C++17 available, you'll need to use a helper function, perhaps with tagged dispatch:

template<class T>
using less_than_t = decltype(std::declval<T>() < std::declval<T>());

template<class T>
using has_less_than = typename is_detected<less_than_t, T>::type;

template<class T>
void do_compare(const T& lhs, const T& rhs, std::true_type) // for operator <
{
    std::cout << "use operator <\n";
}

template<class T>
void do_compare(const T& lhs, const T& rhs, std::false_type)
{
    std::cout << "Something else \n";
}

int main()
{
    Has_Less_Than a{1};
    Has_Less_Than b{2};
    
    do_compare(a, b, has_less_than<Has_Less_Than>{});
    
    Doesnt_Have_Less_Than c{3};
    Doesnt_Have_Less_Than d{4};
    do_compare(c, d, has_less_than<Doesnt_Have_Less_Than>{});
}

Demo


If you have C++20 available, we can easily accomplish this with a concept:

template<class L, class R=L>
concept has_less_than = requires(const L& lhs, const R& rhs)
{
    {lhs < rhs} -> std::same_as<bool>;
};

This concept can be read as "Given two possibly different types, the concept is true if calling operator< between two const references returns a boolean. Otherwise it is false."

You could fiddle with it a bit, e.g., changing same_as to convertible_to (if for some reason you expected operator< to return an integer that could be converted to a boolean.

C++20 Demo

The concept can be used instead of enable_if (requires my_concept<T>), and is also convertible to a compile-time boolean value like true_type and false_type are (so it could be used in if constexpr)

like image 74
AndyG Avatar answered Jan 05 '23 06:01

AndyG