I have a simple SFINAE checker that checks a given type (T
) if it has an operator==
defined for it.
This generally works fairly well, but it does not work for std::pair<>
.
I think I understand why it doesn't work - std::pair<>
appears to unconditionally provide operator==
support, even if the underlying types its a pair of do not.
I'm soliciting views on how to correct my SFINAE operator==
checker, so that it works with pair<>
You can test this code online at https://onlinegdb.com/rmAQS7V091
But for completeness sake, its included here directly as well:
#include <iostream>
#include <functional> // needed for std::equal_to
#include <iterator>
using namespace std;
namespace
{
using namespace std;
/**
* LIFTED -@todo add docs/reference
* from Stroustrup C 11 book - page 800
*/
struct substitution_failure
{
};
/**
* LIFTED -@todo add docs/reference
* from Stroustrup C 11 book - page 800
*/
template < typename T > struct substitution_succeeded:true_type
{
};
template <> struct substitution_succeeded <substitution_failure >:false_type
{
};
#define STROIKA_FOUNDATION_CONFIGURATION_DEFINE_HAS(NAME, XTEST) \
namespace Private_ { \
template <typename T> \
struct NAME##_result_impl { \
template <typename X> \
static auto check (const X& x) -> decltype (XTEST); \
static substitution_failure check (...); \
using type = decltype (check (declval<T> ())); \
}; \
} \
template <typename T> \
using NAME##_result = typename Private_::NAME##_result_impl<T>::type; \
template <typename T> \
struct has_##NAME : integral_constant<bool, not is_same<NAME##_result<T>, substitution_failure>::value> { \
}; \
template <typename ITERABLE> \
constexpr bool Has##NAME##_v = has_##NAME<ITERABLE>::value;
STROIKA_FOUNDATION_CONFIGURATION_DEFINE_HAS (eq, (x == x)); // SEE https://stroika.atlassian.net/browse/STK-749
STROIKA_FOUNDATION_CONFIGURATION_DEFINE_HAS (equal_to, (static_cast<bool> (std::equal_to<X>{}(x, x))));
class SimpleClassWithoutComparisonOperators
{
public:
SimpleClassWithoutComparisonOperators (size_t v);
SimpleClassWithoutComparisonOperators (const
SimpleClassWithoutComparisonOperators
& f);
~SimpleClassWithoutComparisonOperators ();
size_t GetValue () const;
static size_t GetTotalLiveCount ();
//explicit operator size_t () const { return fValue; }
SimpleClassWithoutComparisonOperators operator (const
SimpleClassWithoutComparisonOperators
& rhs) const
{
return SimpleClassWithoutComparisonOperators (fValue rhs.fValue);
}
private:
size_t fValue;
int fConstructed;
static size_t sTotalLiveObjects;
};
static_assert (!has_eq < SimpleClassWithoutComparisonOperators >::value);
using PAIR_ =
std::pair < SimpleClassWithoutComparisonOperators,
SimpleClassWithoutComparisonOperators >;
static_assert (!has_eq < PAIR_ >::value); /// THIS IS WHAT FAILS
}
int
main ()
{
cout << "Hello World";
return 0;
}
CodePudding user response:
To fix std::pair
case, you might specialize your traits for it:
template <typename T1, typename T2>
struct has_eq<std::pair<T1, T2>> :
integral_constant<bool, has_eq<T1>::value && has_eq<T1>::value>
{};
CodePudding user response:
As kenash0625 correctly noted, you need a specialization for std::pair
to manually implement checks that std::pair
itself does not do. However, at this point, I suggest simplifying your implementation with C 17 and without macros:
template<
template<typename...> typename Detector,
typename T,
typename SFINAE = void>
constexpr inline bool is_detected_v = false;
template<
template<typename...> typename Detector,
typename T>
constexpr inline bool is_detected_v<
Detector, T, std::void_t<Detector<T>>> = true;
We can now implement has_eq
like this:
template<typename T>
using has_eq_t = decltype(std::declval<T>() == std::declval<T>());
static_assert(is_detected_v<has_eq_t, std::pair<int, int>>);
We can also write a helper, whose first purpose is to increase code expressiveness:
template<typename T>
constexpr inline bool has_eq_v = is_detected_v<has_eq_t, T>;
static_assert( ! has_eq_v<SimpleClassWithoutComparisonOperators>);
Although the original issue has not been fixed yet, the second purpose of this helper is where we can implement some ad hoc checks for std::pair
. Let's add the specialization:
template<typename T, typename U>
constexpr inline bool has_eq_v<std::pair<T, U>> = has_eq_v<T> && has_eq_v<U>;
Here we can add more and more specializations for such specific cases:
template<typename... Ts>
constexpr inline bool has_eq_v<std::tuple<Ts...>> = (has_eq_v<Ts> && ...);
static_assert ( ! has_eq_v<PAIR_>);
Further reading:
- You should read about the Detection Idioms, this is a more flexible implementation of my
is_detected_v
- C 20 concepts allow us to make our implementation way simpler. I think the concepts are the main reason why we don't have the
std::is_detected
in standard library. It just isn't needed with concepts.
CodePudding user response:
Here is my naive fix: add two lines of code to #define
,
template <typename X> \
static substitution_failure check (std::pair<X,X>); \
altogether:
#define STROIKA_FOUNDATION_CONFIGURATION_DEFINE_HAS(NAME, XTEST) \
namespace Private_ { \
template <typename T> \
struct NAME##_result_impl { \
template <typename X> \
static auto check (const X& x) -> decltype (XTEST); \
static substitution_failure check (...); \
template <typename X> \
static substitution_failure check (std::pair<X,X>); \
using type = decltype (check (declval<T> ())); \
}; \
} \
template <typename T> \
using NAME##_result = typename Private_::NAME##_result_impl<T>::type; \
template <typename T> \
struct has_##NAME : integral_constant<bool, not is_same<NAME##_result<T>, substitution_failure>::value> { \
}; \
template <typename ITERABLE> \
constexpr bool Has##NAME##_v = has_##NAME<ITERABLE>::value;