Is it possible to use elements of a different type than contained in a std::set to perform search and deletion?

情到浓时终转凉″ 提交于 2019-12-01 05:32:52
Daniel Wolf

As of C++14, std::set has templated versions of its lookup functions find, lower_bound, etc. They allow you to pass any object for comparison, as long as the comparer supports it.

This means you can directly pass your void* to find, as long as the comparer supports comparing MetadataThingy and void*.

For more information, see http://en.cppreference.com/w/cpp/container/set/find.

To understand the limitation regarding Compare::is_transparent, I found this StackOverflow question very helpful.

You can do this by using std::find_if and providing a predicate functor.

#include <algorithm>

struct Predicate
{
    void const * const ptr_;
    explicit Predicate(const void* ptr) : ptr_(ptr) {}
    bool operator()(const MetadataThingy& other)
    {
        return ptr_ == other.actual_thingy;
    }
};

m = *std::find_if(thingy_set.begin(), thingy_set.end(), Predicate(&m2));

You can use the iterator returned by std::find_if to remove the element from the set by passing it to set::erase.

No, the signature of map<>::find requires you pass in the key type.

There is, however, a relatively simple workaround. Use boost::optional or std::tr2::optional (from C++1y) to store your non-key data.

struct MetadataThingy {
  void* pBlah;
  optional<rest_of_stuff> rest;
  static MetadataThingy searcher( void* );
  MetadataThingy(...);
};

then call MeatadataThingy::searcher to generate your key value.

Another approach would be to store smart (unique probably) pointers to sub-interfaces, each of which has a "get full data" method. Then, when you want to do a search, create a stub sub-interface that returns nullptr on "get full data".

struct MetadataFull;
struct MetadataRoot {
  virtual MetadataFull* get() = 0;
  virtual MetadataFull const* get() const = 0;
  virtual ~MetadataRoot() {}
};
template<typename T>
struct MetadataFinal: virtual MetadataRoot {
  static_assert( std::is_base_of< T, MetadataFinal<T> >::value, "CRTP failure" );
  virtual MetadataFull* get() { return static_cast<T*>(this); }
  virtual MetadataFull const* get() const { return static_cast<T const*>(this); }
};
struct MetadataStub: virtual MetadataRoot {
  virtual MetadataFull* get() { return nullptr; }
  virtual MetadataFull const* get() const { return nullptr; }
};
struct MetaDataA: virtual MetaDataRoot {
  void* pBlah;
};

struct MetaDataFull: MetaDataA, MetadataFinal<MetaDataFull> {
  // unsorted data
};
struct MetaDataAStub: MetaDataA, MetaDataStub {};

now, this can be done with virtual functions but not virtual inheritance with a bit of finagling if you really need it.

The library does not support the behavior that you ask for, although I have seen other people request the same thing (i.e. providing a templated member function find in ordered associative containers that would use a cross comparator), although this is infrequent.

Your type is unusual in that only one of the member attributes takes part on the value of the object (i.e. is used in the comparison). The compiler cannot know that only some of the members (or which of the members) are part of the value and which are not. although it might be that the objects are not really comparable and you just hammered operator< as a simple way of enabling the use in associative containers.

If that is the case, consider dropping the operator< that does not really compare the MetaThingy objects and also change the data structure to be a std::map<void*,MetaThingy>, which would make the design cleaner at the cost of an extra void* per stored object --it might also be the case that the void* is inside the MetaThingy for the lookup in the set... in which case it might even make more sense and you could provide std::map<void*,MetaInfo>.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!