У меня был тот же вопрос, и в итоге я написал простой класс DereferenceCompare для этой работы.Мне было бы интересно узнать, что другие думают об этом.Суть проблемы в том, что существующие ответы требуют от программиста, использующего ваш набор, доступа к нему необычным способом, который склонен к утечке памяти, то есть путем передачи временного адреса в std::set::find()
или через std::find_if()
.Какой смысл использовать стандартный контейнер, если вы собираетесь получить к нему нестандартный доступ?Boost имеет хорошую библиотеку контейнеров, которая решает эту проблему.Но поскольку в C ++ 14 были введены прозрачные компараторы, вы можете написать собственный компаратор, который заставит std::set::insert()
и std::set:find()
работать, как ожидается, независимо от Boost.Вы можете использовать его как что-то вроде std::set< Foo*, DereferenceCompare<Foo, YourFooComparator> > set_of_foos;
#ifndef DereferenceCompare_H
#define DereferenceCompare_H
#include <type_traits>
// Comparator for std containers that dereferences pointer-like arguments.
// Useful for containers of pointers, smart pointers, etc. that require a comparator.
// For example:
// std::set< int*, DereferenceCompare<int> > myset1;
// int myint = 42;
// myset1.insert(&myint);
// myset1.find(&myint) == myset.end(); // false
// myset1.find(myint) == myset.end(); // false
// myset1.find(42) == myset.end(); // false
// myset1.find(24) == myset.end(); // true, 24 is not in the set
// std::set<int*> myset2;
// myset2.insert(&myint); // compiles, but the set will be ordered according to the address of myint rather than its value
// myset2.find(&myint) == myset.end(); // false
// myset2.find(a) == myset.end(); // compilation error
// myset2.find(42) == myset.end(); // compilation error
//
// You can pass a custom comparator as a template argument. It defaults to std::less<T>.
// The type of the custom comparator is accessible as DereferenceCompare::compare.
// For example:
// struct MyStruct { int val; };
// struct MyStructCompare { bool operator() (const MyStruct &lhs, const MyStruct &rhs) const { return lhs.val < rhs.val; } };
// std::set< MyStruct*, DereferenceCompare<MyStruct, MyStructCompare> > myset;
// decltype(myset)::key_compare::compare comparator; // comparator has type MyStructCompare
template< typename T, class Compare = std::less<T> > class DereferenceCompare
{
#if __cplusplus==201402L // C++14
private:
// Less elegant implementation, works with C+=14 and later.
template<typename U> static constexpr auto is_valid_pointer(int) -> decltype(*(std::declval<U>()), bool()) { return std::is_base_of<T, typename std::pointer_traits<U>::element_type>::value || std::is_convertible<typename std::remove_cv<typename std::pointer_traits<U>::element_type>::type, T>::value; }
template<typename U> static constexpr bool is_valid_pointer(...) { return false; }
public:
template<typename U, typename V> typename std::enable_if<is_valid_pointer<U>(0) && is_valid_pointer<V>(0), bool>::type operator() (const U& lhs_ptr, const V& rhs_ptr) const { return _comparator(*lhs_ptr, *rhs_ptr); } // dereference both arguments before comparison
template<typename U, typename V> typename std::enable_if<is_valid_pointer<U>(0) && !is_valid_pointer<V>(0), bool>::type operator() (const U& lhs_ptr, const V& rhs) const { return _comparator(*lhs_ptr, rhs); } // dereference the left hand argument before comparison
template<typename U, typename V> typename std::enable_if<!is_valid_pointer<U>(0) && is_valid_pointer<V>(0), bool>::type operator() (const U& lhs, const V& rhs_ptr) const { return _comparator(lhs, *rhs_ptr); } // dereference the right hand argument before comparison
#elif __cplusplus>201402L // Better implementation, depends on void_t in C++17.
public:
// SFINAE type inherits from std::true_type if its template argument U can be dereferenced, std::false otherwise.
// Its ::value member is true if the type obtained by dereferencing U, i.e. the pointee, is either derived from T or convertible to T.
// Its ::value is false if U cannot be dereferenced, or it the pointee is neither derived from nor convertible to T.
// Example:
// DereferenceCompare<int>::has_dereference; // std::false_type, int cannot be dereferenced
// DereferenceCompare<int>::has_dereference<int>::is_valid_pointee; // false, int cannot be dereferenced
// DereferenceCompare<int>::has_dereference<int*>; // std::true_type, int* can be dereferenced to int
// DereferenceCompare<int>::has_dereference<int*>::is_valid_pointee; // true, dereferencing int* yields int, which is convertible (in fact, the same type as) int
// DereferenceCompare<int>::has_dereference< std::shared_ptr<int> >::is_valid_pointee; // true, the pattern also works with smart pointers
// DereferenceCompare<int>::has_dereference<double*>::is_valid_pointee; // true, double is convertible to int
// struct Base { }; struct Derived : Base { }; DereferenceCompare<Base>::has_dereference<Derived*>::is_valid_pointee; // true, Derived is derived from Base
// DereferenceCompare<int>::has_dereference<Derived*>; // std::true_type, Derived* can be dereferenced to Derived
// DereferenceCompare<int>::has_dereference<Derived*>::is_valid_pointee; // false, cannot convert from Derived to int nor does Derived inherit from int
template< typename, class = std::void_t<> > struct has_dereference : std::false_type { static constexpr bool is_valid_pointee = false; };
template< typename U > struct has_dereference< U, std::void_t<decltype(*(std::declval<U>()))> > : std::true_type { static constexpr bool is_valid_pointee = std::is_base_of<T, typename std::pointer_traits<U>::element_type>::value || std::is_convertible<typename std::remove_cv<typename std::pointer_traits<U>::element_type>::type, T>::value; };
template<typename U, typename V> typename std::enable_if<has_dereference<U>::is_valid_pointee && has_dereference<V>::is_valid_pointee, bool>::type operator() (const U& lhs_ptr, const V& rhs_ptr) const { return _comparator(*lhs_ptr, *rhs_ptr); } // dereference both arguments before comparison
template<typename U, typename V> typename std::enable_if<has_dereference<U>::is_valid_pointee && !has_dereference<V>::is_valid_pointee, bool>::type operator() (const U& lhs_ptr, const V& rhs) const { return _comparator(*lhs_ptr, rhs); } // dereference the left hand argument before comparison
template<typename U, typename V> typename std::enable_if<!has_dereference<U>::is_valid_pointee && has_dereference<V>::is_valid_pointee, bool>::type operator() (const U& lhs, const V& rhs_ptr) const { return _comparator(lhs, *rhs_ptr); } // dereference the right hand argument before comparison
#endif
public:
typedef /* unspecified --> */ int /* <-- unspecified */ is_transparent; // declaration required to enable polymorphic comparisons in std containers
typedef Compare compare; // type of comparator used on dereferenced arguments
private:
Compare _comparator;
};
#endif // DereferenceCompare_H