Custom hash that works with pointer and reference - c++

I want to create a reusable IdHash and IdEqualTo class that takes const instances (reference, raw pointer, or smart pointer), and returns the hash value or the compare result.
template<class Entity, class Id>
struct IdFunc {
typedef typename std::function<const Id& (const Entity&)> type;
};
template<class Entity, class Id>
struct IdHash {
public:
explicit IdHash(const typename IdFunc<Entity, Id>::type& idFunc) : idFunc_(idFunc) {}
std::size_t operator()(const Entity& o) const {
return std::hash<Id>()(idFunc_(o));
}
private:
typename IdFunc<Entity, Id>::type idFunc_;
};
// IdEqualTo follows the same pattern
My first attempt works with unordered_set<MyClass, IdHash<string, MyClass>, IdEqualTo<...>>. Since MyClass will be a inheritance hierarchy instead of a single type, I need to switch to pointers: unordered_set<unique_ptr<MyClass>, IdHash<string, MyClass>, ...>. Now I need a version of the operator that takes unique_ptr&. I provided the following
std::size_t operator()(const Entity* o) const {
return std::hash<Id>()(idFunc_(*o));
}
hoping unique_ptr<MyClass>& can somehow be converted to MyClass*. It didn't work. Since this utility is supposed to transcend storage type, how can I make it work with reference, raw pointer, or smart pointer?
See code sample.
Thanks.

There is no automatic conversion from smart pointers to raw pointers (though you can use get()).
Specialize your templates for smart pointers, this is the way it is done in standard library and in boost.
template <class Inner, class Id>
struct IdHash<std::unique_ptr<Inner>, Id> {
typedef std::unique_ptr<Inner> PtrType;
std::size_t operator() (const PtrType &pointer) const {
return std::hash<Id>()(idFunc_(pointer.get());
}
};
You may also want to create std::hash<Id> instance only once instead of every time you call operator().

Related

Do C++17 and newer std::allocators work for a dynamic number of custom heaps?

Here is my scenario:
There is a set of Instance objects, the number of the objects being determined at run time.
For each of those instances, a custom heap is created. (Yes, I refer to ::HeapCreate() et. all win32 functions).
struct Instance
{
HANDLE heap;
};
Now, there is some Instance specific State object which has access (a pointer) to the Instance. Its a 1:1 relationship. 1 State per Instance.
struct State
{
Instance * inst;
};
In that State, I want to use STL containers like std::vector and alike, but I want those to use an allocator, which uses the heap created by the instance.
Reading up on the history of std::allocator from the times of C++98 up to C++17 and newer, some old requirements have been dropped, but my take away is, that it is not possible to write such an allocator for my above shown use case. Given that the Allocator would be of the same type each time, but stateful (as in instances using different heap HANDLEs).
So here my question(s):
Can I/ should I even try to use the STL allocator stuff for this use case or better just roll my select few own container classes instead?
I stumbled across that std::pmr C++17 namespace and wonder if there is stuff in there I am not yet acquainted with, which would help me with my use case.
Your answers will be very valuable if you show the "idiomatic" way how to solve this problem in modern (c++17 and newer) C++.
Here, my incomplete and current "work in progress". I will fill in more over time, based on answers and comments and my own progress.
#include <Windows.h>
struct custom_heap_allocator
{
HANDLE heap;
// No default constructor
custom_heap_allocator() = delete;
// an instance is tied to a heap handle.
explicit custom_heap_allocator(HANDLE h)
: heap{ h }
{
}
// can I get away with this....
// copy constructor for same type.
custom_heap_allocator(const custom_heap_allocator& other)
: heap{ other.heap }
{
}
//... or do I need something like this? Or is it somehow covered by rebind?
//template<class U>
//custom_heap_allocator(const custom_heap_allocator<U>& other)
// : heap{ other.heap }
//{
//}
template<class U>
struct rebind {
typedef custom_heap_allocator other;
};
};
template <class T, class U>
constexpr bool operator== (const custom_heap_allocator& a1, const custom_heap_allocator& a2) noexcept
{
return a1.heap == a2.heap;
}
template <class T, class U>
constexpr bool operator!= (const custom_heap_allocator& a1, const custom_heap_allocator& a2) noexcept
{
return a1.heap != a2.heap;
}
Note: shutting down an Instance will simply destroy the heap (no need to call destructors for stuff still on the heap, as those are just simple data and not system resources etc. And all dynamic stuff they hold are also from the same heap instance).
Here is a more complete version of the allocator from your (edited) question.
Some basic unit tests using std::vector and std::list at cpp.sh/4viaj
template<typename T>
struct custom_heap_allocator
{
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
// No default constructor
custom_heap_allocator() = delete;
// An instance is tied to a heap handle.
explicit custom_heap_allocator(HANDLE h)
: heap{ h }
{
}
// Instances are copy-constructable and copy-assignable.
custom_heap_allocator(const custom_heap_allocator&) = default;
custom_heap_allocator& operator=(const custom_heap_allocator&) = default;
// All related allocators share the same heap, regardless of type.
template<class U>
custom_heap_allocator(const custom_heap_allocator<U>& other)
: heap{ other.heap }
{
}
// Allocate and deallocate space for objects using the heap.
T* allocate(size_t n) {
return static_cast<T*>(HeapAlloc(heap, 0, sizeof(T) * n));
}
void deallocate(T* ptr, size_t n) {
HeapFree(heap, 0, ptr);
}
// Construct and destroy objects in previously allocated space.
// This *should* be optional and provided by std::allocator_traits,
// but it looks like some std containers don't use the traits.
template< class U, class... Args >
void construct( U* p, Args&&... args ) {
::new((void *)p) U(std::forward<Args>(args)...);
}
template< class U >
void destroy( U* p ) {
p->~U();
}
// Template for related allocators of different types.
template<class U>
struct rebind {
typedef custom_heap_allocator<U> other;
};
private:
// Heap used for all allocations/deallocations.
HANDLE heap;
// Allow all related types to access our private heap.
template<typename> friend struct custom_heap_allocator;
};

How to store a function object without resorting to std::function?

I am trying to make a generic container that would hold objects and their position:
class Vector;
template <typename T>
class Container
{
public:
void insert(const T& t)
{
insertAtPosition(t.getPosition() ,t);
}
private:
void insertAtPosition(const Vector& v, const T& t);
...
} ;
But what if the users' object position getter is not called getPosition?
How can I make this container generic with respect to the way in which the container internally obtains the position of an item?
So far, I have considered 3 approaches, none of them ideal:
Add a std::function<const Vector& (const T& t)> member to the Container.
This is a clean C++ solution, but this function is going to be called very very often and it may result in noticeable performance decrease.
Add a functor object to the container:
class Vector;
template <typename T, typename TGetPosition>
class Container
{
public:
Container(TGetPosition getPosition): getPosition_(getPosition){}
void insert(const T& t)
{
insertAtPosition(getPosition_(t) ,t);
}
private:
void insertAtPosition(const Vector& v, const T& t);
TGetPosition getPosition_;
} ;
I can use the object generator idiom to make it possible to use lambdas:
template <typename T, typename TGetPosition>
Container<T, TGetPosition> makeContainer(TGetPosition getter)
{
return Container<T, TGetPosition>(getter);
}
...
auto container = makeSimpleContainer<Widget>([](const Widget& w)
{
return w.tellMeWhereYourPositionMightBe();
});
There would be no performance overhead, but it would be impossible to get the type of such a container in certain contexts. For example, you could not create a class that would take such a container as a parameter, since decltype would not work, because lambdas cannot be used in unevaluated contexts.
Use #define GETTER getPosition and the user would just change getPosition to whatever he likes. There are so many things wrong with this approach that I don't even know where to start.
Please, is there some other way to do this? Did I miss anything. Any guidance is most welcome!
EDIT:
Regarding solution 2: I have no idea how could we get a the type of a container created with a lambda function. One way would be:
using TContainer = decltype(makeSimpleContainer<Widget>([](const Widget& w)
{
return w.tellMeWhereYourPositionMightBe();
});)
But this doesn't work, because lambdas cannot be used in unevaluated contexts.
Reasonably usable option would be to expect the context to have position_for():
template <class T> struct Container {
size_t insert(T const& x) {
insertAtPosition(position_for(x), x);
}
};
Vector const& position_for(const Widget& w) {
return ...;
}
Container<Widget> c;
c.insert(Widget());
...but designs where the container generates key from the business object usually do not fly well, as to lookup the object one would need to do a dummy one, which may be expensive.
It seems like your second solution will work even if you need to make a class holding a container:
template <typename Container>
struct SomeClass {
SomeClass(const Container &container) : container(container) { }
Container container;
};
int main()
{
auto container = makeSimpleContainer<Widget>([](const Widget& w)
{
return w.tellMeWhereYourPositionMightBe();
});
SomeClass<decltype(container)> test(container);
}

Using a std::unordered_set of std::unique_ptr

Assume I have a set of unique_ptr:
std::unordered_set <std::unique_ptr <MyClass>> my_set;
I'm not sure what's the safe way to check if a given pointer exists in the set. The normal way to do it may be to call my_set.find (), but what do I pass as a parameter?
All I have from the outside is a raw pointer. So I have to create another unique_ptr from the pointer, pass it to find() and then release() that pointer, otherwise the object would get destructed (twice). Of course, this process can be done in a function, so the caller can pass the raw pointer and I do the conversions.
Is this method safe? Is there a better way to work with a set of unique_ptr?
You can also use a deleter that optionally doesn't do anything.
template<class T>
struct maybe_deleter{
bool _delete;
explicit maybe_deleter(bool doit = true) : _delete(doit){}
void operator()(T* p) const{
if(_delete) delete p;
}
};
template<class T>
using set_unique_ptr = std::unique_ptr<T, maybe_deleter<T>>;
template<class T>
set_unique_ptr<T> make_find_ptr(T* raw){
return set_unique_ptr<T>(raw, maybe_deleter<T>(false));
}
// ...
int* raw = new int(42);
std::unordered_set<set_unique_ptr<int>> myset;
myset.insert(set_unique_ptr<int>(raw));
auto it = myset.find(make_find_ptr(raw));
Live example.
Note that the ability to do heterogenous lookups on standard containers is subject of some proposals.
http://cplusplus.github.io/LWG/lwg-proposal-status.html lists
N3465 Adding heterogeneous comparison lookup to associative containers for TR2 (Rev 2) [Handle with N3573]
N2882 id.
N3573 Heterogenous extensions to unordered containers [Handle with N3465]
Especially the latter looks like it would cover your use case.
For now, here is an IMO not very pretty but working alternative workaround (O(n)):
#include <iterator>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <memory>
#include <cassert>
struct MyClass {};
template <typename T>
struct RawEqualTo
{
RawEqualTo(T const* raw) : raw(raw) {}
bool operator()(T const* p) const
{ return raw == p; }
bool operator()(std::unique_ptr<T> const& up) const
{ return raw == up.get(); }
private:
T const* raw;
};
using namespace std;
int main()
{
std::unordered_set <std::unique_ptr <MyClass>> my_set;
my_set.insert(std::unique_ptr<MyClass>(new MyClass));
my_set.insert(std::unique_ptr<MyClass>(new MyClass));
auto raw = my_set.begin()->get();
bool found = end(my_set) != std::find_if(begin(my_set), end(my_set), RawEqualTo<MyClass>(raw));
assert(found);
raw = new MyClass;
found = end(my_set) != std::find_if(begin(my_set), end(my_set), RawEqualTo<MyClass>(raw));
assert(!found);
delete raw;
}
Warning It's also very inefficient, of course.
You can use a std::map<MyClass*, std::unique_ptr<MyClass>> instead of a set. Then you can add elements like this:
std::unique_ptr<MyClass> instance(new MyClass);
map.emplace(instance.get(), std::move(instance));
If the goal is constant time for the look up, I don't think that
there is a solution.
std::unordered_set<std::unique_ptr<MyClass>>::find requires an
std::unique_ptr<MyClass> as argument. You will have to either change
the container, or change the contained type.
One possibility might be to replace std::unique_ptr with
std::shared_ptr, and change the rest of the code so that all
MyClass are put into a shared_ptr as soon as they are created,
and are only manipulated through shared pointers. Logically,
this is probably more coherent anyway: unique_ptr pretty much
implies (by its name, as well as its semantics) that there
aren't other pointers to the object. On the other hand, you may
not be able to use shared_ptr, if e.g. MyClass has pointers to
other MyClass, which may build a cycle.
Otherwise, if you can accept O(lg n) access, rather than
constant access (the difference generally doesn't become
noticeable until the tables are fairly large), you can use an
std::vector<MyClass>, using std::lower_bound to keep it
sorted. Unlike std::unordered_set<>::find, std::lower_bound
does not require the target value to have the same type as the
value_type of the sequence; all you have to do is to ensure
that they are comparable, say by providing a Compare object
along the lines of:
class MyClassPtrCompare
{
std::less<MyClass const*> cmp;
public:
bool operator()( std::unique_ptr<MyClass> const& lhs,
std::unique_ptr<MyClass> const& rhs ) const
{
return cmp( lhs.get(), rhs.get() );
}
bool operator()( MyClass const* lhs,
std::unique_ptr<MyClass> const& rhs ) const
{
return cmp( lhs, rhs.get() );
}
bool operator()( std::unique_ptr<MyClass> const& lhs,
MyClass const* rhs ) const
{
return cmp( lhs.get(), rhs );
}
bool operator()( MyClass const* lhs,
MyClass const* rhs ) const
{
return cmp( lhs, rhs );
}
};
Insertion may involve a number of moves, but moving
a std::unique_ptr should be fairly cheap, and the improved
locality of this solution might offset the additional runtime
costs it otherwise imposes.
If you can use Abseil, do it:
absl::flat_hash_set<std::unique_ptr<MyClass>> my_set;
just works :)
Here is the proper way to do it in C++20 with "Heterogeneous lookup for unordered containers" available:
struct Hash {
using is_transparent = void;
template <class P>
size_t operator()(const P& p) const {
return std::hash<P>{}(p);
}
};
struct KeyEqual {
using is_transparent = void;
template <class P, class Q>
bool operator()(const P& lhs, const Q& rhs) const {
return std::to_address(lhs) == std::to_address(rhs);
}
};
std::unordered_set<std::unique_ptr<MyClass>, Hash, KeyEqual> my_set;
More on the topic (in Russian): https://www.coursera.org/learn/c-plus-plus-brown/supplement/TtrLN/unordered-set-unique-ptr

Creating a template predicate class requiring a pointer to method function, and ensuing compiler errors

I'm building a series of predicates that duplicate lots of code, and so are being changed into a single template function class based on the std::unary_function. The idea is that my class interface requires methods such as Element_t Element() and std::string Name() to be defined, so the predicate template arguments are the object type and a value type to which comparison will be made as follows:
// generic predicate for comparing an attribute of object pointers to a specified test value
template <class U, typename R>
class mem_fun_eq : public std::unary_function <U*, bool> {
private:
typedef R (U::*fn_t)();
fn_t fn;
R val;
public:
explicit mem_fun_eq (fn_t f, R& r) : fn(f), val(r) { }
bool operator() (U * u) const {
return (u->*fn)() == val;
}
};
Thus, if I have:
class Atom {
public:
const Element_t& Element() const { return _element; }
const std::string& Name() const { return _name; }
};
I would like to perform a search on a container of Atoms and check for either the Name or Element equality using my template predicate like so:
typedef std::string (Atom::*fn)() const;
Atom_it it = std::find_if( _atoms.begin(), _atoms.end(), mem_fun_eq <Atom, std::string> ((fn)&Atom::Name, atomname));
but compiling this returns the following error on the std::find_if line:
error: address of overloaded function with no contextual type information
Also, trying to form the same predicate for a check of the Element() as such:
typedef Atom::Element_t& (Atom::*fn)() const;
Atom_it it = std::find_if(_atoms.begin(), _atoms.end(), mem_fun_eq <Atom, Atom::Element_t> ((fn)&Atom::Element, elmt);
creates a different error!
error: no matching function for call to ‘mem_fun_eq<Atom, Atom::Element_t>::mem_fun_eq(Atom::Element_t& (Atom::*)()const, const Atom::Element_t&)’
note: candidates are: mem_fun_eq<U, R>::mem_fun_eq(R (U::*)(), R&) [with U = Atom, R = Atom::Element_t]
note: mem_fun_eq<Atom, Atom::Element_t>::mem_fun_eq(const mem_fun_eq<Atom, Atom::Element_t>&)
Firstly, am I reinventing the wheel with this predicate? Is there something in the STL that I've missed that does the same job in a single class? I can always break the predicate down into several more specific ones, but I'm trying to avoid that.
Secondly, can you help me with the compiler errors?
I don't know of any easy way to do this using the bits provided with the STL. There is probably some clever boost way, using iterator adapters, or boost::lambda, but personally I wouldn't go that way.
Obviously C++0x lambdas will make all this easy.
Your problem is attempting to cast a function like this:
const std::string&(Atom::*)()
into a function like this:
std::string (Atom::*)()
If you replace your typedef R (U::*fn_t)(); with typedef const R& (U::*fn_t)() const; then it should work.
The following avoids this problem and also provides type inference so that you can just write mem_fun_eq(&Atom::Name, atomname). It compiles for me, although I haven't tested it.
template<typename U, typename R, typename S>
class mem_fun_eq_t : public std::unary_function<U const*, bool>
{
private:
R (U::*fn_)() const;
S val_;
public:
mem_fun_eq_t(R (U::*fn )() const, S val) : fn_(fn), val_(val){}
bool operator()(U * u)
{
return (u->*fn_)() == val_;
}
};
template<typename U, typename R, typename S>
mem_fun_eq_t<U, R, S> mem_fun_eq(R (U::*fn)() const, S val)
{
return mem_fun_eq_t<U, R, S>(fn, val);
}
Have you thought of trying to mix in a mem_fun_ref or mem_fun object in place of the member function call?
Basically, you call on mem_fun to create an object that accepts two arguments T* and a template argument to the function A if it has one (or void if it doesn't). Hence you combine it like so:
template<typename MemFunc, typename CompareType, typename T>
struct MyPredicate{
MyPredicate(MemFunc _functionObj, CompareType _value)
: m_Value(_value),
m_Function(_functionObj){}
bool operator()(const T &_input){
return m_Value == m_Function(_input);
}
private:
MemFunc m_Function;
CompareType m_Value;
};
Edit:
Ok, that's not completely working so why not have:
struct NamePred: binary_function<Atom*,string,bool>{
bool operator()(Atom *_obj, string _val){
return _obj->Name() == _val;
};
};
then use bind2nd
find_if( atoms.begin(), atoms.end(), bind2nd( NamePred, "yo" ) );

C++ smart pointer const correctness

I have a few containers in a class, for example, vector or map which contain
shared_ptr's to objects living on the heap.
For example
template <typename T>
class MyExample
{
public:
private:
vector<shared_ptr<T> > vec_;
map<shared_ptr<T>, int> map_;
};
I want to have a public interface of this class that sometimes returns shared_ptrs to const objects (via shared_ptr<const T>) and sometimes shared_ptr<T> where I allow the caller to mutate the objects.
I want logical const correctness, so if I mark a method as const, it cannot change the objects on the heap.
Questions:
1) I am confused by the interchangeability of shared_ptr<const T> and shared_ptr<T>. When someone passes a shared_ptr<const T> into the class, do I:
Store it as a shared_ptr<T> or shared_ptr<const T> inside the container?
OR
Do I change the map, vector types (e.g. insert_element(shared_ptr<const T> obj)?
2) Is it better to instantiate classes as follows: MyExample<const int>? That seems unduly restrictive, because I can never return a shared_ptr<int>?
shared_ptr<T> and shared_ptr<const T> are not interchangable. It goes one way - shared_ptr<T> is convertable to shared_ptr<const T> but not the reverse.
Observe:
// f.cpp
#include <memory>
int main()
{
using namespace std;
shared_ptr<int> pint(new int(4)); // normal shared_ptr
shared_ptr<const int> pcint = pint; // shared_ptr<const T> from shared_ptr<T>
shared_ptr<int> pint2 = pcint; // error! comment out to compile
}
compile via
cl /EHsc f.cpp
You can also overload a function based on a constness. You can combine to do these two facts to do what you want.
As for your second question, MyExample<int> probably makes more sense than MyExample<const int>.
I would suggest the following methotology:
template <typename T>
class MyExample
{
private:
vector<shared_ptr<T> > data;
public:
shared_ptr<const T> get(int idx) const
{
return data[idx];
}
shared_ptr<T> get(int idx)
{
return data[idx];
}
void add(shared_ptr<T> value)
{
data.push_back(value);
}
};
This ensures const-correctness. Like you see the add() method does not use <const T> but <T> because you intend the class to store Ts not const Ts. But when accessing it const, you return <const T> which is no problem since shared_ptr<T> can easily be converted to shared_ptr<const T>. And sice both get() methods return copies of the shared_ptr's in your internal storage the caller can not accidentally change the object your internal pointers point to. This is all comparable to the non-smart pointer variant:
template <typename T>
class MyExamplePtr
{
private:
vector<T *> data;
public:
const T *get(int idx) const
{
return data[idx];
}
T *get(int idx)
{
return data[idx];
}
void add(T *value)
{
data.push_back(value);
}
};
If someone passes you a shared_ptr<const T> you should never be able to modify T. It is, of course, technically possible to cast the const T to just a T, but that breaks the intent of making the T const. So if you want people to be able to add objects to your class, they should be giving you shared_ptr<T> and no shared_ptr<const T>. When you return things from your class you do not want modified, that is when you use shared_ptr<const T>.
shared_ptr<T> can be automatically converted (without an explicit cast) to a shared_ptr<const T> but not the other way around. It may help you (and you should do it anyway) to make liberal use of const methods. When you define a class method const, the compiler will not let you modify any of your data members or return anything except a const T. So using these methods will help you make sure you didn't forget something, and will help users of your class understand what the intent of the method is. (Example: virtual shared_ptr<const T> myGetSharedPtr(int index) const;)
You are correct on your second statement, you probably do not want to instantiate your class as <const T>, since you will never be able to modify any of your Ts.
one thing to realize is that:
tr1::shared_ptr<const T> is mimicking the functionality of T const * namely what it points to is const, but the pointer itself isn't.
So you can assign a new value to your shared pointer, but I would expect that you wouldn't be able to use the dereferenced shared_ptr as an l-value.
Prologue
The const qualifier changes the behaviour of std::shared_ptr, just like it affects the legacy C pointers.
Smart pointers should be managed and stored using the right qualifiers at all times to prevent, enforce and help programmers to treat them rightfully.
Answers
When someone passes a shared_ptr<const T> into the class, do I store it as a shared_ptr<T> or shared_ptr<const T> inside the vector and map or do I change the map, vector types?
If your API accepts a shared_ptr<const T>, the unspoken contract between the caller and yourself is that you are NOT allowed to change the T object pointed by the pointer, thus, you have to keep it as such in your internal containers, e.g. std::vector<std::shared_ptr<const T>>.
Moreover, your module should NEVER be able/allowed to return std::shared_ptr<T>, even though one can programatically achieve this (See my answer to the second question to see how).
Is it better to instantiate classes as follows: MyExample<const int>? That seems unduly restrictive, because I can never return a shared_ptr<int>?
It depends:
If you designed your module so that objects passed to it should not change again in the future, use const T as the underlying type.
If you your module should be able to return non-const T pointers, you should use T as your underlying type and probably have two different getters, one that returns mutable objects (std::shared_ptr<T>) and another that returns non-mutable objects (std::shared_ptr<const T>).
And, even though I hope we just agreed you should not return std::shared_ptr<T> if you have a const T or std::shared_ptr<const T>, you can:
const T a = 10;
auto a_ptr = std::make_shared<T>(const_cast<T>(a));
auto b_const_ptr = std::make_shared<const T>();
auto b_ptr = std::const_pointer_cast<T>(b_const_ptr);
Full blown example
Consider the following example that covers all the possible permutations of const with std::shared_ptr:
struct Obj
{
int val = 0;
};
int main()
{
// Type #1:
// ------------
// Create non-const pointer to non-const object
std::shared_ptr<Obj> ptr1 = std::make_shared<Obj>();
// We can change the underlying object inside the pointer
ptr1->val = 1;
// We can change the pointer object
ptr1 = nullptr;
// Type #2:
// ------------
// Create non-const pointer to const object
std::shared_ptr<const Obj> ptr2 = std::make_shared<const Obj>();
// We cannot change the underlying object inside the pointer
ptr2->val = 3; // <-- ERROR
// We can change the pointer object
ptr2 = nullptr;
// Type #3:
// ------------
// Create const pointer to non-const object
const std::shared_ptr<Obj> ptr3 = std::make_shared<Obj>();
// We can change the underlying object inside the pointer
ptr3->val = 3;
// We can change the pointer object
ptr3 = nullptr; // <-- ERROR
// Type #4:
// ------------
// Create const pointer to non-const object
const std::shared_ptr<const Obj> ptr4 = std::make_shared<const Obj>();
// We can change the underlying object inside the pointer
ptr4->val = 4; // <-- ERROR
// We can change the pointer object
ptr4 = nullptr; // <-- ERROR
// Assignments:
// ------------
// Conversions between objects
// We cannot assign to ptr3 and ptr4, because they are const
ptr1 = ptr4 // <-- ERROR, cannot convert 'const Obj' to 'Obj'
ptr1 = ptr3;
ptr1 = ptr2 // <-- ERROR, cannot convert 'const Obj' to 'Obj'
ptr2 = ptr4;
ptr2 = ptr3;
ptr2 = ptr1;
}
Note: The following is true when managing all types of smart pointers. The assignment of pointers might differ (e.g. when handling unique_ptr), but the concept it the same.
In the meantime, there exists std::experimental::propagate_const that addresses exactly this issue.
#include <vector>
#include <map>
#include <memory>
#include <experimental/propagate_const>
#include <type_traits>
template <typename T>
class MyExample
{
public:
template <typename U>
using pointer_t = std::experimental::propagate_const<std::shared_ptr<U>>;
std::vector<pointer_t<T> > vec_;
std::map<pointer_t<T>, int> map_;
};
int main() {
auto x = std::make_shared<int>(42);
MyExample<int> e;
e.vec_.push_back(x);
// non-const getter will propagate mutability through the pointer
{
auto& test = e.vec_[0];
static_assert(std::is_same<int&, decltype(*test)>::value);
}
// const-getter will propagate const through the pointer
{
MyExample<int> const& ec = e;
auto& test = ec.vec_[0];
static_assert(std::is_same<int const&, decltype(*test)>::value);
}
return 0;
}
https://godbolt.org/z/ej3hPsqMo
If you are uncomfortable using the experimental namespace, or if you are using MSVC (as far as i know MSVC hasn't implemented this feature yet), you can implement your own version of propagate_const. A protoype could look like this:
template <typename Ptr>
class propagate_const
{
public:
using value_type = typename std::remove_reference<decltype(*Ptr{})>::type;
template <
typename T,
typename = std::enable_if_t<std::is_convertible<T, Ptr>::value>
>
constexpr propagate_const(T&& p) : ptr{std::forward<T>(p)} {}
constexpr value_type& operator*() { return *ptr; }
constexpr value_type const& operator*() const { return *ptr; }
constexpr value_type& operator->() { return *ptr; }
constexpr value_type const& operator->() const { return *ptr; }
private:
Ptr ptr;
};
https://godbolt.org/z/rbKcr3M66