C++ Get reference of object inside vector using function - c++

Can you get a reference to an object that is within a vector through a function? I could do this with pointers easily, but you know, we're all obsessed here with "Don't use pointers".
This is a simple example. The absolute limitation is it must be done from a function call (so that function call can return false if not found).
// Example program
#include <iostream>
#include <string>
#include <vector>
class Dev {
public:
Dev(){}
std::string name;
};
void geter(std::vector<Dev> &devs, Dev &a){
a = devs.at(0);
}
int main()
{
Dev d;
d.name = "original name";
std::vector<Dev> devs;
devs.push_back(d);
Dev a;
geter(devs, a);
a.name = "new name";
std::cout << d.name; // still prints "original name"
}

Can you get a reference to an object that is within a vector through a function?
Yes, but you cannot return that reference via a function parameter. A reference can be bound to an element only at the point of initializing the reference. Once you are inside a function, it is too late to initialize the function's parameters, too late to bind a reference. You can return a reference via a function's return value, but not via an output parameter.
References also fail to cover the "not found" possibility, as a reference must be bound to something.
The language feature that allows the functionality you are looking for is called a "pointer".
References are useful if they can be bound during initialization, never need to change what they are bound to, and never need to be in a state of not being bound. The first parameter to your geter function is an example of this.
Pointers are useful if they need to point to different objects during their lifetime, or if they might need to be in an "unbound" state (a.k.a. be null). Think of a pointer as a reference that can be reseated (refer to a different object than it did before) and that can be unseated (refer to no object). The intended functionality of the second parameter to your geter function is an example of this.
I could do this with pointers easily,
Good. You know the right tool for the job. Do it.
but you know, we're all obsessed here with "Don't use pointers".
No, I do not know that. In fact, that is bad advice when stated that broadly. Pointers still have their place in modern C++. The "obsession" you probably are referring to is "don't use owning pointers". That is, don't use a pointer if you have to remember to delete the thing to which the pointer points. If there is no ownership involved (i.e. no responsibility for freeing memory), then there is nothing inherently bad about using pointers. In fact, pointers are often a more appropriate choice than references when "does not exist" is a valid possibility (just remember to check for null, which your logic would call for anyway).
Note: There are other "obsessions" that fall under "don't use pointers", but I don't see another that is relevant here. For the sake of an example: there is also "don't use a pointer when a reference will get the job done." This is good advice, but in this case a reference will not get the job done.

Nah. Doing it via parameters is silly. It's the year 2020 and you have std::optional and such.
I'd do it as follows. First, some helper code:
#include <type_traits>
#include <vector>
template <typename T> using const_qualified_value_type_impl =
std::conditional_t<std::is_const_v<T>,
typename std::add_const_t<typename T::value_type>,
typename T::value_type>;
template <typename T> using const_qualified_value_type =
const_qualified_value_type_impl<std::remove_reference_t<T>>;
static_assert(std::is_same_v<const_qualified_value_type<std::vector<int>>, int>, "");
static_assert(std::is_same_v<const_qualified_value_type<const std::vector<int>>, const int>, "");
#include <functional>
#include <optional>
template <typename T> class optional_ref
{
std::optional<std::reference_wrapper<T>> val;
public:
template <typename ...Args> constexpr optional_ref(Args &&...args) :
val(std::forward<Args>(args)...) {}
constexpr explicit operator bool() const { return static_cast<bool>(val); }
constexpr auto has_value() const { return val.has_value(); }
constexpr auto &value() const { return val.value().get(); }
constexpr auto &get() const { return val.value().get(); }
constexpr operator T&() const { return val.value().get(); }
};
Now we have an optional_ref type (a very rudimentary one, but still),
and we can use it when creating the get_first getter:
template <typename C>
auto get_first(C && container) -> optional_ref<const_qualified_value_type<C>>
{
auto const begin = container.begin();
static_assert(std::is_reference_v<decltype(*begin)>, "*begin() must return a reference");
if (begin == container.end()) return {};
return *begin;
}
Now a basic test:
#include <cassert>
int main()
{
std::vector<int> vect;
assert(!get_first(vect));
vect.push_back(0);
assert(get_first(vect));
assert(get_first(vect) == 0);
int &first = get_first(vect).value();
++first;
assert(get_first(vect) == 1);
const std::vector<int> cempty;
assert(!get_first(cempty));
const std::vector<int> cnon_empty{0};
assert(get_first(cnon_empty));
assert(get_first(cnon_empty) == 0);
auto &cfirst = get_first(cnon_empty).value();
static_assert(std::is_same_v<decltype(cfirst), const int &>, "");
}
That way:
get_first's return value is bool-convertible in boolean contexts, i.e. you can use it as if it were a bool to check if a reference is valid.
get_first's return value is convertible to a reference to the value stored in the container, and the value type is automatically const-qualified if the container is const-qualified. That typically is what you'd want, although don't take my word for it.
Ideally, optional_ref should be implemented using a pointer, but for demonstration purposes it was quicker to reuse std::optional and std::reference_wrapper.
But the above works (in a rudimentary fashion) under gcc, clang and msvc.
You'd use it as follows:
auto val = get_first(foo);
if (val.has_value())
{
auto &v = val.value();
// use v
}

Related

Return tuple that preserves references but copies r-values

I need to make a lambda that returns a tuple to be used for sorting a vector of objects. The tuple gathers a collection of attributes from the object. Some attributes are references to large objects that should not be copied (f() in the code below), others are small r-values (g() in the code below). What is the simplest or most idiomatic way of writing the lambda?
#include <vector>
#include <tuple>
#include <algorithm>
struct A {
std::vector<int> const &f() const;
int g() const;
};
void example() {
auto sort_key = [](A const &a) {
//return std::tuple{a.f(), a.g()}; // copies the vector from f()
//return std::tie(a.f(), a.g()); // can't bind the int from g()
//return std::forward_as_tuple(a.f(), a.g()); // returns a reference to the int from g(), which expires when this function returns
return std::tuple<std::vector<int> const &, int>{a.f(), a.g()}; // works, but requires specifying the types
};
std::vector<A> v;
std::sort(v.begin(), v.end(), [&](A const &a, A const &b) {
return sort_key(a) < sort_key(b);
});
}
I'm embarrassed to say forward_as_tuple was my first attempt, and after a long debug session I realized it was returning int&& for the second attribute, a value that goes out of scope. Writing it this way was muscle memory because forward_as_tuple(...) < forward_as_tuple(...) is perfectly fine by my understanding: since everything is all on one line of code we will have lifetime extension for all the references.
Explicitly specifying the types - reference for first attribute, value for second - when constructing the tuple works, and that's what I'm doing now, but I'd prefer something cleaner. (Okay, truth be told it's not so bad, but I'm asking because I'm curious if there's something better.)
I could probably work up a helper function, call it forward_references_but_copy_rvalues, but is there something already in the standard library that could do this?

Is there a way to return either a new object or reference to existing object from a function?

I am trying to write a function, which can either return a reference to an existing object passed as a first argument (if it is in correct state) or create and return a new object using literal passed as a second argument (default).
It would be even better if a function could take not only literal, but also another existing object as a second (default) argument and return a reference to it.
Below is a trivial implementation, but it does a lot of unneeded work:
If called with lvalue as a second (default) argument, it calls a copy constructor of an argument, that is selected for return. Ideally a reference to an object should be returned.
If called with literal as a second (default) argument, it calls constructor, copy constructor and destructor, even if second (default) argument is not selected for return. It would be better if an object is constructed and returned as rvalue reference without calling copy constructor or destructor.
std::string get_or_default(const std::string& st, const std::string& default_st) {
if (st.empty()) return default_st
else return st;
}
Is there a way to accomplish this more efficiently, while still keeping simple for caller? If I am correct, this requires a function to change return type based on run-time decision made inside a function, but I cannot think of a simple solution for caller.
I'm not 100% sure I understood the combinations of requirements but:
#include <iostream>
#include <string>
#include <type_traits>
// if called with an rvalue (xvalue) as 2:nd arg, move or copy
std::string get_or_default(const std::string& st, std::string&& default_st) {
std::cout << "got temporary\n";
if(st.empty())
return std::move(default_st); // rval, move ctor
// return std::forward<std::string>(default_st); // alternative
else
return st; // lval, copy ctor
}
// lvalue as 2:nd argument, return the reference as-is
const std::string& get_or_default(const std::string& st,
const std::string& default_st) {
std::cout << "got ref\n";
if(st.empty()) return default_st;
else return st;
}
int main() {
std::string lval = "lval";
// get ref or copy ...
decltype(auto) s1 = get_or_default("", "temporary1");
decltype(auto) s2 = get_or_default("", std::string("temporary2"));
decltype(auto) s3 = get_or_default("", lval);
std::cout << std::boolalpha;
std::cout << std::is_reference_v<decltype(s1)> << "\n";
std::cout << std::is_reference_v<decltype(s2)> << "\n";
std::cout << std::is_reference_v<decltype(s3)> << "\n";
}
Output:
got temporary
got temporary
got ref
false
false
true
Edit: Made a slightly more generic version after OP:s testing. It can use a lambda, like
auto empty_check = [](const std::string& s) { return s.empty(); };
to test if the first argument is empty.
template<typename T, typename F>
T get_or_default(const T& st, T&& default_st, F empty) {
if(empty(st)) return std::move(default_st);
// return std::forward<T>(default_st); // alternative
else return st;
}
template<typename T, typename F>
const T& get_or_default(const T& st, const T& default_st, F empty) {
if(empty(st)) return default_st;
else return st;
}
Well, there a few things here.
To express what you ask for directly you can use something like std::variant<std::string, std::string&> as your function return type. Although I have not checked if variant can store a reference.
Or some equivalent from a third party library. either<> ?
You can also write your own class wrapping string and string ref.
(Not an real code)
struct StringOrRef {
enum class Type {Value, Ref} type;
union {
std::string value;
std::reference_wrapper<const std::string> ref;
};
...
};
Check the topic: discriminating union in C++.
But I think there is a bigger problem with your example!
Please consider the ownership of data. std::string takes ownership of data passed. That is why it copy data. Thus when your function returns - the called is sure it had a data and don't need to worry about it as long as (s)he holds the value.
In case you design a function to return a reference to passed argument value - you need to make sure that the value is used within the same lifespan as the argument passed (to which the ref is returned)
So consider:
StringOrRef func(strging const& a, string const& b);
...
StringOrRef val;
{ // begin scope:
SomeStruct s = get_defaul();
val = func("some value", s.get_ref_to_internal_string());
}// end of val scope
val; // is in scope but may be referencing freed data.
The problem here is the temporary object SomeStruct s. if it's member function get_ref_to_internal_string() -> string& returns a ref to a string field of that object (which is often the way it is implemented) - then when s goes out of scope - tha ref becomes invalid. that is - it is referencing freed memory which may have been given to some other objects.
And if you capture that reference in val - val will be referencing invalid data.
You will be lucky if it all end in access violation or a signal. At worst your program continues but will be crashing randomly.

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...

c++ variant class member stored by reference

I am trying to experiment with std::variant. I am storing an std::variant as a member of a class. In the below code, things work fine if the variant is stored by value, but does not work (for the vector case, and for custom objects too) if the variant is stored by reference. Why is that?
#include <variant>
#include <vector>
#include <iostream>
template<typename T>
using VectorOrSimple = std::variant<T, std::vector<T>>;
struct Print {
void operator()(int v) { std::cout << "type = int, value = " << v << "\n"; }
void operator()(std::vector<int> v) const { std::cout << "type = vector<int>, size = " << v.size() << "\n"; }
};
class A {
public:
explicit A(const VectorOrSimple<int>& arg) : member(arg) {
print();
}
inline void print() const {
visit(Print{}, member);
}
private:
const VectorOrSimple<int> member; // const VectorOrSimple<int>& member; => does not work
};
int main() {
int simple = 1;
A a1(simple);
a1.print();
std::vector<int> vector(3, 1);
A a2(vector);
a2.print();
}
See http://melpon.org/wandbox/permlink/vhnkAnZhqgoYxU1H for a working version, and http://melpon.org/wandbox/permlink/T5RCx0ImTLi4gk5e for a crashing version with error : "terminate called after throwing an instance of 'std::bad_variant_access'
what(): Unexpected index"
Strangely, when writing a boost::variant version of the code with the member stored as a reference, it works as expected (prints vector size = 3 twice) with gcc7.0 (see here http://melpon.org/wandbox/permlink/eW3Bs1InG383vp6M) and does not work (prints vector size = 3 in constructor and then vector size = 0 on the subsequent print() call, but no crash) with clang 4.0 (see here http://melpon.org/wandbox/permlink/2GRf2y8RproD7XDM).
This is quite confusing. Can someone explain what is going on?
Thanks.
It doesn't work because this statement A a1(simple); creates a temporary variant object!
You then proceed to bind said temporary to your const reference. But the temporary goes out of scope immediately after the construction of a1 is over, leaving you with a dangling reference. Creating a copy works, obviously, since it always involves working with a valid copy.
A possible solution (if the performance of always copying worries you) is to accept a variant object by-value, and then move it into your local copy, like so:
explicit A(VectorOrSimple<int> arg) : member(std::move(arg)) {
print();
}
This will allow your constructor to be called with either lvalues or rvalues. For lvalues your member will be initialized by moving a copy of the source variant, and for rvalues the contents of the source will just be moved (at most) twice.
Variants are objects. They contain one of a set of types, but they are not one of those types.
A reference to a variant is a reference to the variant object, not a reference to one of the contained types.
A variant of reference wrappers may be what you want:
template<class...Ts>
using variant_ref=std::variant<std::reference_wrapper<Ts>...>;
template<typename T>
using VectorOrSimple = std::variant<T, std::vector<T>>;
template<typename T>
using VectorOrSimpleRef = variant_ref<T, std::vector<T>>;
template<typename T>
using VectorOrSimpleConstRef = variant_ref<const T, const std::vector<T>>;
Now store VectorOfSimpleConstRef<int>. (Not const&). And take one in the constructor as well.
Also modify Print to take by const& to avoid needlessly copying that std::vector when printing.

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.