std::map of objects or object pointers? - c++

I have two options to create a std map. I can work with both the types of map.
1. std::map<A, std::string>
2. std::map<A*, std::string>
where A is a class object
Later in the code I will have to perform a find operation.
1. std::map<A, std::string> myMap1;
if(myMap1.find(A_obj) != myMap1.end())
{
}
2. std::map<A*, std::string> myMap2;
if(myMap2.find(A_obj_ptr) != myMap2.end())
{
}
I want to know which one is recommend to create.
In which of these two, would I not have to overload any operators in class A for find operation to work. Which of these would have problems on insert operation when any operators are not overloaded.
If it helps, this is class A
class A
{
private:
std::vector<std::string> m_member;
public:
A(std::vector<std::string> input);
};

Note that these two samples are only functionally equivalent if A instances are singletons. Otherwise it's very possible that two A values which are equal in value but different in address. This would lead to different semantics.
Personally I prefer the std::map<A, std::string> version because the semantics of it are crystal clear. The keys have equality semantics and there is no potentially for a dangling or nullptr value. The std::map<A*, std::string> version comes with a host of questions for the developer looking through the code
Who owns the key values?
Are all instances of A singletons? If not how do I ensure the A I'm looking for is the A* value that is stored?
When are the keys freed?

First option is preferable. For second option, we need to make sure that keys (pointers here) are protected. May be shared pointers will help. Other issue is that the map will be shorted w.r.t. the address of the A objects and that might not be very useful. Below sample demonstrates how the comparator can be defined or the default comparator can be overridden:
class A
{
public:
int a;
};
namespace std
{
template<>
struct less<A*>
{
bool operator()(const A* const a, const A* const b) const{
return a->a < b->a;
}
};

Related

Get a vector of map keys without copying?

I have a map of objects where keys are std::string. How can I generate a vector of keys without copying the data?
Do I need to change my map to use std::shared_ptr<std::string> as keys instead? Or would you recommend something else?
My current code goes like this:
MyClass.h
class MyClass {
private:
std::map <std::string, MyType> my_map;
public:
const std::vector<std::string> MyClass::getKeys() const;
}
MyClass.cpp
const std::vector<std::string> MyClass::getKeys() const
{
std::vector<std::string> keys = std::vector<std::string>();
for (const auto& entry : my_map)
keys.push_back(entry.first); // Data is copied here. How can I avoid it?
return keys;
}
As suggested by Kevin, std::views::keys was pretty much made for this. The view it produces is a lightweight object, not much more than a pointer to the range argument, which solves the ownership and lifetime issues. Iterating over this view is identical to iterating over the map, the only difference is that the view's iterator dereferences to the first element of the underlying map value.
As for some code, it is pretty simple:
// ...
#include <ranges>
class MyClass {
private:
std::map<std::string, MyType> my_map;
public:
std::ranges::view auto getKeys() const;
};
std::ranges::view auto MyClass::getKeys() const
{
return std::views::keys(my_map);
}
Since ranges and concepts go hand in hand, I've used the std::ranges::view to constrain the auto return type. This is a useful way to let the compiler and user of your function know that it will return a particular category of object, without having to specify a potentially complicated type.

How to return a private pointer to a list of pointers as const?

I have a pointer to a list of pointers, as a private variable. I also have a getter that returns the pointer to the list. I need to protect it from changes.
I couldn't find how to use reinterpret_cast or const_cast on this.
class typeA{
shared_ptr<list<shared_ptr<typeB>>> l;
public:
shared_ptr<list<shared_ptr<const typeB>>> getList(){return (l);};
};
The compiler returns:
error: could not convert ‘((typeA*)this)->typeA::x’ from ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<typeB> > >’ to ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<const typeB> > >’|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|
It seems as const shared_ptr<list<shared_ptr<typeB>>> and shared_ptr<const list<shared_ptr<typeB>>> work fine.
Is it possible to do return l as a complete const, like:
const shared_ptr<const list<shared_ptr<const typeB>>>
or at least like:
shared_ptr<list<shared_ptr<const typeB>>>
?
References instead of pointers is not an option. To declare l as shared_ptr<list<shared_ptr<const typeB>>> also is not a wanted solution.
EDIT: no 'int' anymore.
It seems as it is not possible exactly what I wanted, but the suggested solutions are good. Yes, copying pointers is acceptable.
My bad i didn't put typeB immediately. I am aware of some advantages of references over pointers, but I hoped there is some similar solution.
You can create a new list of const int's from your original list and return that:
std::shared_ptr<std::list<std::shared_ptr<const int>>> getList(){
return std::make_shared<std::list<std::shared_ptr<const int>>>(l->begin(), l->end());
}
If you want to prevent people from making changes to the returned list, make it const too:
std::shared_ptr<const std::list<std::shared_ptr<const T>>> getList(){
return std::make_shared<const std::list<std::shared_ptr<const T>>>(l->cbegin(), l->cend());
}
The shared pointer returned by this function does not point to the original list but to the newly created list.
An alternative may be to provide iterators that, when dereferenced, returns const T& (where T is the type you actually store). That way there will be no need to copy the whole list every time you want to go though it. Example:
#include <iostream>
#include <list>
#include <memory>
struct example {
int data;
example(int x) : data(x) {}
};
template <class T>
class typeA {
std::shared_ptr<std::list<std::shared_ptr<T>>> l = std::make_shared<std::list<std::shared_ptr<T>>>();
public:
template< class... Args >
void add( Args&&... args ) {
l->emplace_back(std::make_shared<T>(std::forward<Args>(args)...));
}
// a very basic iterator that can be extended as needed
struct const_iterator {
using uiterator = typename std::list<std::shared_ptr<T>>::const_iterator;
uiterator lit;
const_iterator(uiterator init) : lit(init) {}
const_iterator& operator++() { ++lit; return *this; }
const T& operator*() const { return *(*lit).get(); }
bool operator!=(const const_iterator& rhs) const { return lit != rhs.lit; }
};
const_iterator cbegin() const noexcept { return const_iterator(l->cbegin()); }
const_iterator cend() const noexcept { return const_iterator(l->cend()); }
auto begin() const noexcept { return cbegin(); }
auto end() const noexcept { return cend(); }
};
int main() {
typeA<example> apa;
apa.add(10);
apa.add(20);
apa.add(30);
for(auto& a : apa) {
// a.data = 5; // error: assignment of member ‘example::data’ in read-only object
std::cout << a.data << "\n";
}
}
When you convert a pointer-to-nonconst to a pointer-to-const, you have two pointers. Furthermore, a list of pointers-to-nonconst is a completely different type from a list of pointers-to-const.
Thus, if you want to return a pointer to a list of pointers-to-const, what you must have is a list of pointers-to-const. But you don't have such list. You have a list of pointers-to-nonconst and those list types are not interconvertible.
Of course, you could transform your pointers-to-nonconst into a list of pointers-to-const, but you must understand that it is a separate list. A pointer to former type cannot point to the latter.
So, here is an example to transform the list (I didn't test, may contain typos or mistakes):
list<shared_ptr<const int>> const_copy_of_list;
std::transform(l->begin(), l->end(), std::back_inserter(const_copy_of_list),
[](auto& ptr) {
return static_pointer_cast<const int>(ptr);
});
// or more simply as shown by Ted:
list<shared_ptr<const int>> const_copy_of_list(l->begin(), l->end());
Since we have created a completely new list, which cannot be pointed by l, it makes little sense to return a pointer. Let us return the list itself. The caller can wrap the list in shared ownership if the need it, but don't have to when it is against their needs:
list<shared_ptr<const int>> getConstCopyList() {
// ... the transorm above
return const_copy_of_list;
}
Note that while the list is separate, the pointers inside still point to the same integers.
As a side note, please consider whether shared ownership of an int object makes sense for your program - I'm assuming it is a simplification for the example.
Also reconsider whether "References instead of pointers is not an option" is a sensible requirement.
You problem squarely lies at
but I do not want to mix references and pointers. It is easier and cleaner to have just pointers.
What you are finding here is that statement is wrong. A list<TypeB> can bind a const list<TypeB> & reference, and none of the list's members will allow any modification of the TypeB objects.
class typeA {
std::vector<typeB> l;
public:
const std::vector<typeB> & getList() const { return l; };
};
If you really really must have const typeB, you could instead return a projection of l that has added const, but that wouldn't be a Container, but instead a Range (using the ranges library voted into C++20, see also its standalone implementation)
std::shared_ptr<const typeB> add_const(std::shared_ptr<typeB> ptr)
{
return { ptr, ptr.get() };
}
class typeA {
std::vector<std::shared_ptr<typeB>> l;
public:
auto getList() const { return l | std::ranges::transform(add_const); };
};
Another alternative is that you can wrap your std::shared_ptrs in something like std::experimental::propagate_const, and just directly return them.
What you have here is a VERY complex construct:
shared_ptr<list<shared_ptr<typeB>>> l;
This is three levels of indirection, of which two have reference counting lifetime management, and the third is a container (and not memory-contiguous at that).
Naturally, given this complex structure, it is not going to be easy to convert it to another type:
shared_ptr<list<shared_ptr<const typeB>>>
Notice that std::list<A> and std::list<const A> are two distinct types by design of standard library. When you want to pass around non-modifying handles to your containers, you are usually supposed to use const_iterators.
In your case there is a shared_ptr on top of the list, so you can't use iterators if you want that reference counting behavior.
At this point comes the question: do you REALLY want that behavior?
Are you expecting a situation where your typeA instance is destroyed, but you still have some other typeA instances with the same container?
Are you expecting a situation where all your typeA instances sharing the container are destroyed, but you still have some references to that container in other places of your runtime?
Are you expecting a situation where the container itself is destroyed, but you still have some references to some of the elements?
Do you have any reason at all to use std::list instead of more conventional containers to store shared pointers?
If you answer YES to all the bullet points, then to achieve your goal you'll probably have to design a new class that would behave as a holder for your shared_ptr<list<shared_ptr<typeB>>>, while only providing const access to the elements.
If, however, on one of the bullet points your answer is NO, consider redesigning the l type. I suggest starting with std::vector<typeB> and then only adding necessary modifications one by one.
The problem with templates is that for any
template <typename T>
class C { };
any two pairs C<TypeA> and C<TypeB> are totally unrelated classes – this is even the case if TypeA and TypeB only differ in const-ness.
So what you actually want to have is technically not possible. I won't present a new workaround for now, as there are already, but try to look a bit further: As denoted in comments already, you might be facing a XY problem.
Question is: What would a user do with such a list? She/he might be iterating over it – or access single elements. Then why not make your entire class look/behave like a list?
class typeA
{
// wondering pretty much why you need a shared pointer here at all!
// (instead of directly aggregating the list)
shared_ptr<list<shared_ptr<typeB>>> l;
public:
shared_ptr<list<shared_ptr<typeB>>>::const_iterator begin() { return l->begin(); }
shared_ptr<list<shared_ptr<typeB>>>::const_iterator end() { return l->end(); }
};
If you used a vector instead of a list, I'd yet provide an index operator:
shared_ptr<typeB /* const or not? */> operator[](size_t index);
Now one problem yet remains unsolved so far: The two const_iterators returned have an immutable shared pointer, but the pointee is still mutable!
This is a bit of trouble - you'll need to implement your own iterator class now:
class TypeA
{
public:
class iterator
{
std::list<std::shared_ptr<int>>::iterator i;
public:
// implementation as needed: operators, type traits, etc.
};
};
Have a look at std::iterator for a full example – be aware, though, that std::iterator is deprecated, so you'll need to implement the type-traits yourself.
The iterator tag to be used would be std::bidirectional_iterator_tag or random_access_iterator_tag (contiguous_iterator_tag with C++20), if you use a std::vector inside.
Now important is how you implement two of the needed operators:
std::shared_ptr<int const> TypeA::iterator::operator*()
{
return std::shared_ptr<int const>(*i);
}
std::shared_ptr<int const> TypeA::iterator::operator->()
{
return *this;
}
The other operators would just forward the operation to the internal iterators (increment, decrement if available, comparison, etc).
I do not claim this is the Holy Grail, the path you need to follow under all circumstances. But it is a valuable alternative worth to at least consider...

Raw pointer lookup for sets of unique_ptrs

I often find myself wanting to write code like this:
class MyClass
{
public:
void addObject(std::unique_ptr<Object>&& newObject);
void removeObject(const Object* target);
private:
std::set<std::unique_ptr<Object>> objects;
};
However, much of the std::set interface is kind of useless with std::unique_ptrs since the lookup functions require std::unique_ptr parameters (which I obviously don't have because they're owned by the set itself).
I can think of two main solutions to this.
Create a temporary unique_ptr for lookup. For example, the above removeObject() could be implemented like:
void MyClass::removeObject(const Object* target)
{
std::unique_ptr<Object> targetSmartPtr(target);
objects.erase(targetSmartPtr);
targetSmartPtr.release();
}
Replace the set with a map of raw pointers to unique_ptrs.
// ...
std::map<const Object*, std::unique_ptr<Object>> objects;
};
However, both seem slightly stupid to me. In solution 1, erase() isn't noexcept, so the temporary unique_ptr might delete the object it doesn't really own, and 2 requires double the storage for the container unnecessarily.
I know about Boost's pointer containers, but their current features are limited compared to modern C++11 standard library containers.
I was recently reading about C++14 and came across "Adding heterogeneous comparison lookup to associative containers". But form my understanding of it, the lookup types must be comparable to the key types, but raw pointers aren't comparable to unique_ptrs.
Anyone know of a more elegant solution or an upcoming addition to C++ that solves this problem?
In C++14, std::set<Key>::find is a template function if Compare::is_transparent exists. The type you pass in does not need to be Key, just equivalent under your comparator.
So write a comparator:
template<class T>
struct pointer_comp {
typedef std::true_type is_transparent;
// helper does some magic in order to reduce the number of
// pairs of types we need to know how to compare: it turns
// everything into a pointer, and then uses `std::less<T*>`
// to do the comparison:
struct helper {
T* ptr;
helper():ptr(nullptr) {}
helper(helper const&) = default;
helper(T* p):ptr(p) {}
template<class U, class...Ts>
helper( std::shared_ptr<U,Ts...> const& sp ):ptr(sp.get()) {}
template<class U, class...Ts>
helper( std::unique_ptr<U, Ts...> const& up ):ptr(up.get()) {}
// && optional: enforces rvalue use only
bool operator<( helper o ) const {
return std::less<T*>()( ptr, o.ptr );
}
};
// without helper, we would need 2^n different overloads, where
// n is the number of types we want to support (so, 8 with
// raw pointers, unique pointers, and shared pointers). That
// seems silly:
// && helps enforce rvalue use only
bool operator()( helper const&& lhs, helper const&& rhs ) const {
return lhs < rhs;
}
};
then use it:
typedef std::set< std::unique_ptr<Foo>, pointer_comp<Foo> > owning_foo_set;
now, owning_foo_set::find will accept unique_ptr<Foo> or Foo* or shared_ptr<Foo> (or any derived class of Foo) and find the correct element.
Outside of C++14, you are forced to use the map to unique_ptr approach, or something equivalent, as the signature of find is overly restrictive. Or write your own set equivalent.
Another possibility, close to the accepted answer, but a little different and simplified.
We can exploit the fact that standard comparator std::less<> (with no template arguments) is transparent. Then, we can supply our own comparison functions in the global namespace:
// These two are enough to be able to call objects.find(raw_ptr)
bool operator<(const unique_ptr<Object>& lhs, const Object* rhs) {
return std::less<const Object*>()(lhs.get(), rhs);
}
bool operator<(const Object* lhs, const unique_ptr<Object>& rhs) {
return std::less<const Object*>()(lhs, rhs.get());
}
class MyClass
{
// ...
private:
std::set<std::unique_ptr<Object>, std::less<>> objects; // Note std::less<> here
};
You can try to use boost::multi_index_container with additional indexing by Object*.
Something like this:
typedef std::unique_ptr<Object> Ptr;
typedef multi_index_container<
Ptr,
indexed_by<
hashed_unique<Ptr>,
ordered_unique<const_mem_fun<Ptr,Object*,&Ptr::get> >
>
> Objects;
Fore more information see Boost Multi-index Containers documentation
Or may be you can use std::shared_ptr everywhere, or use raw pointers in set instead?
Why you need to lookup by raw pinter? If you store it anywhere and check that object with this pointer is valid then better to use std::shared_ptr for storing in container and std::weak_ptr for other objects. In this case before usage you don't need lookup by raw pointer at all.
While definitely a hack, I just realized it's possible to construct a temporary "dumb" unique_ptr with placement new and not risk de-allocation. removeObject() could be written something like this:
void MyClass::removeObject(const Object* target)
{
alignas(std::unique_ptr<Object>)
char dumbPtrData[sizeof(std::unique_ptr<Object>)];
objects.erase(
*::new (dumbPtrData) std::unique_ptr<Object>(const_cast<Object *>(target)));
}
This solution would work for std::unordered_set, std::map keys, and std::unordered_map keys as well, all using standard C++11 only, with practically zero unnecessary overhead.
UPDATE 2: Yakk is correct, there is no way to do this with standard C++11 containers without significant compromises. Either something will run in linear time in the worst case or there are those workarounds that you write in your question.
There are two workarounds that I would consider.
I would try a sorted std::vector, similarly to boost::container::flat_set. Yes, the inserts / erases will be linear time in the worst case. Still, it might be much faster than you probably think: Contiguous containers are very cache friendly compared to node based containers, such as std::set. Please read what they write at boost::container::flat_set. Whether this compromise is acceptable for you, I cannot tell / measure.
Others also mentioned std::share_ptr. I personally try to avoid them, mainly because "a shared pointer is as good as a global variable" (Sean Parent). Another reason why I don't use them is because they are heavy weight, partly because of all the multi-threading stuff that I usually don't need. However, boost::shared_ptr, when BOOST_SP_DISABLE_THREADS is defined, removes all that overhead associated with multi-threading. I believe using boost::shared_ptr would be the easiest solution in your case.
UPDATE: As Yakk kindly pointed out, my approach has linear time complexity... :(
(The first version.)
You can do it by passing a custom comparator to std::lower_bound(). Here is a rudimentary implementation how:
#include <algorithm>
#include <cassert>
#include <iostream>
#include <memory>
#include <set>
#include <string>
using namespace std;
template <typename T>
class Set {
private:
struct custom_comparator {
bool operator()(const unique_ptr<T>& a, const T* const & b){
return a.get() < b;
}
} cmp;
set<unique_ptr<T>> objects; // decltype at begin() and end()
// needs objects to be declared here
public:
auto begin() const -> decltype(objects.begin()) { return objects.begin(); }
auto end() const -> decltype(objects.end() ) { return objects.end(); }
void addObject(unique_ptr<T>&& newObject) {
objects.insert(move(newObject));
}
void removeObject(const T* target) {
auto pos = lower_bound(objects.begin(), objects.end(), target, cmp);
assert (pos!=objects.end()); // What to do if not found?
objects.erase(pos);
}
};
void test() {
typedef string T;
Set<T> mySet;
unique_ptr<T> a{new T("a")};
unique_ptr<T> b{new T("b")};
unique_ptr<T> c{new T("c")};
T* b_ptr = b.get();
mySet.addObject(move(a));
mySet.addObject(move(b));
mySet.addObject(move(c));
cout << "The set now contains: " << endl;
for (const auto& s_ptr : mySet) {
cout << *s_ptr << endl;
}
mySet.removeObject(b_ptr);
cout << "After erasing b by the pointer to it:" << endl;
for (const auto& s_ptr : mySet) {
cout << *s_ptr << endl;
}
}
int main() {
test();
}
You're using unique pinters here. This means, your set has unique ownership of objects. Now, this should mean that if object does exist, it's either in the set or you have unique pointer with it. You don't even need to look up the set in this case.
But to me it looks like it's not hte case. I suppose you're better off with shared pointer in this case. Just store shared pointers and pass them around since someone beside this set clearly stores them.

C++ , how to return a reference to the member variable from iterator

There is the following class B derived from std::vector
class B: public std::vector <unsigned int>
{
public:
B() : std::vector <unsigned int> ( 0 ) {}
};
and a class A witten as follows:
class A
{
private:
B b;
double x;
public:
B & getB () {return b;}
B const & getB() const {return b;}
bool operator() ( const A & a ) const
{
return a < a.x;
}
};
Why it is impossible to return a refererence to the variable b of some object A stored in std::list from its iterator (and how to do that)?
int main ()
{
std::set <A > alist;
std::set <A> ::iterator i_alist = alist.begin();
for (; i_alist != alist.end(); i_list++)
{
B &il = (*i_alist).getB(); //Compiler error
B &il2 = i_alist->getB(); //Compiler error
il.push_back(10); //Modify il and concurrently B
}
}
Compiler error:
Error 1 error C2440: 'initializing' : cannot convert from 'const B' to 'B &' d:\test.cpp
Thanks for your help...
Edit question:
The possible solution using const_cast :
B &il2 = const_cast <B&> ( i_alist->getB() );
This has very little to do with std::vector and everything to do with std::set. You store your objects in a std::set. Elements of std::set are not modifiable from the outside. Once you inserted an element into a set, you cannot change it.
For this reason, std::set::iterator might (and will) evaluate to a constant object, even if it is not a const_iterator (the language specification actually allows std::set::iterator and std::set::const_iterator to refer to the same type, but does not require it). I.e. in your example *i_list is a const-qualified object of type const A. The compiler will call the const version of getB, which returns const B &. You are apparently hoping to break through that constness, expecting the non-const version of getB to be called.
There's no way around it, unless you decide to use some sort of hack (const_cast etc.) to remove the constness from the set element and thus make the compiler to call non-const version of getB.
The const_cast-based hack-solution would look as
B &il = const_cast<A &>(*i_alist).getB();
or you can remove the constness later, from the returned B reference
B &il = const_cast<B &>((*i_alist).getB());
B &il2 = const_cast<B &>(i_alist->getB());
There are some things that look weird about your example:
Never derive from std::vector. It's not designed for that use. And there normally is no reason to do so, except laziness, i.e. to avoid some typing.
What is that A::operator() for? Looks like some strange way to provide a Comparator for the std::set you are using. Normally you don't alter a class just because you stuff it into a std::set somewhere. If you really want to be able to compare your A's (generally, not just for the set), then write a proper free bool operator<(A const&, A const&); If it's just for the set, write a custom Comparator in the place where you use the set. (That comparator should not need to access A's privates)
What is the A::x used for? Only for comparison or even only for the comparison inside the set? If it's just that, consider to use a std::map<double, A> or even a std::map<double, B> where you put your x as the keys and the actual objects as the values.
You see, there are a lot of open questions I ask, and maybe the solution (using a map) does not even fit your problem. That is because you did not show us the real code you have but only some example with meaningless names. So we only see what you are trying to do, not what you actually want to achieve with your code.
PS: maybe you don't even need an ordered container in the first place, it could suffice to stuff all the objects into a std::vector and then std::sort it. Depends on the kind of objects you really have (the A's of your example are cheap to copy/swap) and of the ways you use the container - how the insertions are interleaved with the loops and so on.

Why is set::find not a template?

With template functions from <algorithm> you can do things like this
struct foo
{
int bar, baz;
};
struct bar_less
{
// compare foo with foo
bool operator()(const foo& lh, const foo& rh) const
{
return lh.bar < rh.bar;
}
template<typename T> // compare some T with foo
bool operator()(T lh, const foo& rh) const
{
return lh < rh.bar;
}
template<typename T> // compare foo with some T
bool operator()(const foo& lh, T rh) const
{
return lh.bar < rh;
}
};
int main()
{
foo foos[] = { {1, 2}, {2, 3}, {4, 5} };
bar_less cmp;
int bar_value = 2;
// find element {2, 3} using an int
auto it = std::lower_bound(begin(foos), end(foos), bar_value, cmp);
std::cout << it->baz;
}
In std::set methods like find you have to pass an object of type set::key_type which often forces you to create a dummy object.
set<foo> foos;
foo search_dummy = {2,3}; // don't need a full foo object;
auto it = foos.find(search_dummy);
It would be so helpful if one can call just foos.find(2). Is there any reason why find can't be a template, accepting everything that can be passed to the less predicate. And if it is just missing, why isn't it in C++11 (I think it isn't).
Edit
The main question is WHY isn't it possible and if it was posiible, WHY decided the standard not to provide it. A a second question you can propose workarounds :-) (boost::multi_index_container crosses my mind just now, which provides key extraction from value types)
Another Example with a more expensive to construct value type. The key name is part of the type and should not be used as a copy in maps key;
struct Person
{
std::string name;
std::string adress;
std::string phone, email, fax, stackoferflowNickname;
int age;
std::vector<Person*> friends;
std::vector<Relation> relations;
};
struct PersonOrder
{
// assume that the full name is an unique identifier
bool operator()(const Person& lh, const Person& rh) const
{
return lh.name < rh.name;
}
};
class PersonRepository
{
public:
const Person& FindPerson(const std::string& name) const
{
Person searchDummy; // ouch
searchDummy.name = name;
return FindPerson(searchDummy);
}
const Person& FindPerson(const Person& person) const;
private:
std::set<Person, PersonOrder> persons_;
// what i want to avoid
// std::map<std::string, Person> persons_;
// Person searchDummyForReuseButNotThreadSafe;
};
std::find_if works on an unsorted range. So you can pass any predicate you want.
std::set<T> always uses the Comparator template argument (std::less<T> by default) to maintain the order of the collection, as well as find elements again.
So if std::set::find was templated, it would have to require that you only pass a predicate that observes the comparator's total ordering.
Then again, std::lower_bound and all the other algorithms that work on sorted ranges already require exactly that, so that would not be a new or surprising requirement.
So, I guess it's just an oversight that there's no find_if() (say) on std::set. Propose it for C++17 :) (EDIT:: EASTL already has this, and they used a far better name than I did: find_as).
That said, you know that you shouldn't use std::set, do you? A sorted vector will be faster in most cases and allows you the flexibility you find lacking in std::set.
EDIT: As Nicol pointed out, there're implementations of this concept in Boost and Loki (as well as elsewhere, I'm sure), but seeing as you can't use their main advantage (the built-in find() method), you would not lose much by using a naked std::vector.
The standard states that std::set::find has logarithmic time complexity. In practice this is accomplished by implementing std::set as a binary search tree, with a strict weak ordering comparison used as sorting criteria. Any look-up that didn't satisfy the same strict weak ordering criteria wouldn't satisfy logarithmic complexity. So it is a design decision: if you want logarithmic complexity, use std::set::find. If you can trade complexity for flexibility, use std::find_if on the set.
They've provided for what you want, but in a rather different way than you're considering.
Actually, there are two different ways: one is to build a constructor for the contained class that 1) can be used implicitly, and 2) requires only the subset of elements that you really need for comparison. With this in place, you can do a search for foods.find(2);. You will end up creating a temporary object from 2, then finding that temporary object, but it will be a true temporary. Your code won't have to deal with it explicitly (anywhere).
Edit: What I'm talking about here would be creating an instance of the same type as you're storing in the map, but (possibly) leaving any field you're not using as a "key" un-initialized (or initialized to something saying "not present"). For example:
struct X {
int a; // will be treated as the key
std:::string data;
std::vector<int> more_data;
public:
X(int a) : a(a) {} // the "key-only" ctor
X(int a, std::string const &d, std::vector<int> const &m); // the normal ctor
};
std::set<X> s;
if (s.find(2)) { // will use X::X(int) to construct an `X`
// we've found what we were looking for
}
Yes, when you construct your X (or what I've called X, anyway) with the single-argument constructor, chances are that what you construct won't be usable for anything except searching.
end edit]
The second, for which the library provides more direct support is often a bit simpler: if you're really only using some subset of elements (perhaps only one) for searching, then you can create a std::map instead of std::set. With std::map, searching for an instance of whatever you've specified as the key type is supported explicitly/directly.
key_type is a member type defined in set containers as an alias of Key, which is the first template parameter and the type of the elements stored in the container.
See documentation.
For user-defined types there is no way for the library to know what the key type is. It so happens that for your particular use case the key type is int. If you use a set< int > s; you can call s.find( 2 );. However, you will need to help the compiler out if you want to search a set< foo > and want to pass in only an integer (think how will the set's ordering work between foo and an int).
Because if you want to do std::find(2) you'll have to define how int will compare with foo in addition to the comparison between two foos. But since int and foo are different types, you will need actually two additional functions:
bool operator<(int, foo);
bool operator<(foo, int);
(or some other logical equivalent pair).
Now, if you do that, you are actually defining a bijective function between int and foo and you could as well simply use a std::map<int, foo> and be done.
If you still don't want the std::map but you want the benefits of a sorted search, then the dummy object is actually the easiest way to go.
Granted, the standard std::set could provide a member function, to which you pass a function that receives a foo and return -1, 0, 1 if it is less, equal or greater than the searched one... but that's not the C++ way. And note that even bsearch() takes two arguments of the same type!