如何修复检测operator==存在性的SFINAE校验器使其支持std::pair
问题根源
你遇到的问题是C20之前标准库std::pair的operator==设计导致的:C11到C++17标准中,std::pair的operator==是无条件声明的非约束模板,SFINAE检测阶段只会检查表达式x == x的声明合法性,不会深入到函数模板实例化阶段校验内部逻辑,因此哪怕pair的元素类型不支持==运算,你的校验器也会误判pair支持operator==。
解决方案
方案一:兼容C++11及以上版本,对std::pair做特化处理
新增pair类型判断辅助模板,对has_eq做偏特化,递归校验pair的两个元素类型是否都支持operator==,如果需要支持std::tuple也可以按照相同逻辑扩展:
// 新增辅助模板,判断是否是std::pair特化 template <typename T> struct is_pair : std::false_type {}; template <typename T1, typename T2> struct is_pair<std::pair<T1, T2>> : std::true_type {}; // 对has_eq做偏特化,pair类型需同时满足两个元素都支持== template <typename T1, typename T2> struct has_eq<std::pair<T1, T2>> : std::integral_constant<bool, has_eq<T1>::value && has_eq<T2>::value> {};
把这段代码加在STROIKA_FOUNDATION_CONFIGURATION_DEFINE_HAS宏定义之后即可,原来的static_assert(!has_eq<PAIR_>::value)就会正常通过。
方案二:C++20及以上版本,使用concept通用检测
C++20标准为标准库所有容器的比较运算符增加了约束,同时引入了concept语法,无需特殊处理pair类型就能得到正确的检测结果:
template <typename T> concept has_eq = requires(const T& a, const T& b) { { a == b } -> std::convertible_to<bool>; }; // 使用方式和原来一致 static_assert(!has_eq<SimpleClassWithoutComparisonOperators>); static_assert(!has_eq<std::pair<SimpleClassWithoutComparisonOperators, SimpleClassWithoutComparisonOperators>>);
修正后完整可运行代码
#include <iostream> #include <functional> #include <utility> #include <type_traits> using namespace std; namespace { using namespace std; struct substitution_failure {}; 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, typename = void> \ 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)); STROIKA_FOUNDATION_CONFIGURATION_DEFINE_HAS (equal_to, (static_cast<bool> (std::equal_to<X>{}(x, x)))); // ========== 新增代码开始 ========== template <typename T> struct is_pair : std::false_type {}; template <typename T1, typename T2> struct is_pair<std::pair<T1, T2>> : std::true_type {}; template <typename T1, typename T2> struct has_eq<std::pair<T1, T2>> : std::integral_constant<bool, has_eq<T1>::value && has_eq<T2>::value> {}; // ========== 新增代码结束 ========== class SimpleClassWithoutComparisonOperators { public: SimpleClassWithoutComparisonOperators (size_t v) : fValue(v) {} SimpleClassWithoutComparisonOperators (const SimpleClassWithoutComparisonOperators& f) = default; ~SimpleClassWithoutComparisonOperators () = default; size_t GetValue () const { return fValue; } static size_t GetTotalLiveCount () { return sTotalLiveObjects; } SimpleClassWithoutComparisonOperators operator+ (const SimpleClassWithoutComparisonOperators& rhs) const { return SimpleClassWithoutComparisonOperators (fValue + rhs.fValue); } private: size_t fValue; int fConstructed = 0; static size_t sTotalLiveObjects; }; size_t SimpleClassWithoutComparisonOperators::sTotalLiveObjects = 0; static_assert (!has_eq < SimpleClassWithoutComparisonOperators >::value, "SimpleClass shouldn't have =="); using PAIR_ = std::pair < SimpleClassWithoutComparisonOperators, SimpleClassWithoutComparisonOperators >; static_assert (!has_eq < PAIR_ >::value, "Pair of SimpleClass shouldn't have =="); // 现在可以正常通过 } int main () { cout << "检测全部通过" << endl; return 0; }
内容的提问来源于stack exchange,提问作者lewis
相关产品推荐
相关产品推荐

