I am storing the ownership of some objects inside an unordered_set, using unique_ptrs.
But I don't know a good way to erase one of them from the set, when the time comes.
Code looks something like this:
typedef unique_ptr<MyType> MyPtr;
unordered_set<MyPtr> owner;
MyPtr p = make_unique<MyType>("foo")
MyType *pRaw = p.get();
owner.insert(std::move(p));
// Later ...
// I want to do something like this (cannot be written as-is, of course):
// owner.erase(pRaw);
Is there a way to do this?
I can, of course, iterate the entire set with begin() and end(), but the whole point of putting them in the set is to make these lookups efficient.
Some things I have thought of already:
Use shared_ptr. This is the wrong abstraction for my case. Ownership is unique.
Use raw pointers, and forget about unique_ptr. This abandons all the advantages that unique_ptr provides.
Find the bucket with unordered_set::begin(key). As far as I know, there is no way for me to create a key that will match the unique_ptr I want to delete. But I'm happy to be proven wrong (:
(In truth, I solved this using eastl::unordered_set, with its find_as function for custom keys)
This is a tough case. erase has an overload that takes a const key_type& parameter, so we can try to create a "stale" unique_ptr to get the hash value of the element to be erased:
template <typename T>
auto erase(std::unordered_set<std::unique_ptr<T>>& set, T* ptr)
{
std::unique_ptr<T> stale_ptr{ptr};
auto ret = set.erase(stale_ptr);
stale_ptr.release();
return ret;
}
(live demo)
This version, however, is not exception safe in general, because release will not be called if set.erase throws an exception. This is not a problem in this case, since std::equal_to<std::unique_ptr<T>>::operator() never throws exception. In the general case, we can abuse unique_ptr (!) to enforce exception safety by ensuring that release is called regardless of whether the function is exited normally or exceptionally:
template <typename T>
auto erase(std::unordered_set<std::unique_ptr<T>>& set, T* ptr)
{
std::unique_ptr<T> stale_ptr{ptr};
auto release = [](std::unique_ptr<T>* p) { p->release(); };
std::unique_ptr<std::unique_ptr<T>, decltype(release)> release_helper{&stale_ptr, release};
return set.erase(stale_ptr);
}
(live demo)
In C++20, std::unordered_set::find can use equivalent key with transparent hash and KeyEqual, then you might do something similar to:
struct MyHash
{
using is_transparent = void;
auto operator()(MyType* p) const { return std::hash<MyType*>{}(p); }
auto operator()(const MyPtr& p) const { return std::hash<MyType*>{}(p.get()); }
};
struct MyEqual
{
using is_transparent = void;
template <typename LHS, typename RHS>
auto operator()(const LHS& lhs, const RHS& rhs) const
{
return AsPtr(lhs) == AsPtr(rhs);
}
private:
static const MyType* AsPtr(const MyType* p) { return p; }
static const MyType* AsPtr(const MyPtr& p) { return p.get(); }
};
int main()
{
std::unordered_set<MyPtr, MyHash, MyEqual> owner;
MyPtr p = std::make_unique<MyType>();
MyType *pRaw = p.get();
owner.insert(std::move(p));
auto it = owner.find(pRaw);
if (it != owner.end()) {
owner.erase(it);
}
}
Related
I tried to iterate over an std::optional:
for (auto x : optionalValue)
{
...
}
With the expectation of it doing nothing if optionalValue is empty but doing one iteration if there is a value within, like it would work in Haskell (which arguably made std::optional trendy):
forM optionalValue
( \x ->
...
)
Why can't I iterate an optional? Is there another more standard C++ method to do this?
std::optional does not have a begin() and end() pair. So you cannot range-based-for over it. Instead just use an if conditional.
See Alternative 2 for the closest thing to what you want to do.
Edit: If you have a temporary from a call result you don't have to check it explicitly:
if (auto const opt = function_call()) {
do_smth(*opt);
}
The check for static_cast<bool>(opt) is done implicitly by if.
Alternative 1
Another alternative is to not use an std::optional<T> but std::variant<std::monostate, T>. You can then either use the overloaded idiom or create a custom type to handle the monostate:
template <typename F>
struct MaybeDo {
F f;
void operator()(std::monostate) const {}
template <typename T>
void operator()(T const& t) const { f(t); }
};
which will allow you to visit the value with some function:
std::variant<std::monostate, int> const opt = 7;
std::visit(MaybeDo{[](int i) { std::cout << i << "\n"; }}, opt);
Alternative 2
You can also wrap optional in a thing that allows you to iterate over it. Idea:
template <typename T>
struct IterateOpt {
std::optional<T> const& opt;
struct It {
std::optional<T> const* p;
It& operator++() {
p = nullptr;
return *this;
}
It operator++(int) {
return It{nullptr};
}
auto const& operator*() const { **p; }
};
auto begin() const {
if (opt) return It{&opt};
else end();
}
auto end() const {
return It{nullptr};
}
};
This is a crude sketch of how to do this and probably requires some love to work on different situations (like a non-const optional).
You can use this to "iterate" over the optional:
for (auto const& v: IterateOpt{function_call()}) {
do_smth(v);
)
template<typename T>
struct raster {
std::vector<T> v;
template<typename U, typename = std::enable_if_t<sizeof(T) == sizeof(U)>>
raster(raster<U>&& other){
// What code goes here?
}
}
Suppose we have raster<uint32_t> r such that r.v.size() is in the millions, and a guarantee that all its elements are within int32_t's range. Is it possible for raster<int32_t>::raster(raster<uint32_t>&& other) to avoid copying the memory backing other.v?
Or should I just do something like *reinterpret_cast<raster<int32_t>*>(&r) instead of calling that constructor?
There is no legal way to do this in C++; you can only move buffers from a std::vector to another std::vector of the exact same type.
There are a variety of ways you can hack this. The most illegal and evil would be
std::vector<uint32_t> evil_steal_memory( std::vector<int32_t>&& in ) {
return reinterpret_cast< std::vector<uint32_t>&& >(in);
}
or something similar.
A less evil way would be to forget it is a std::vector at all.
template<class T>
struct buffer {
template<class A>
buffer( std::vector<T,A> vec ):
m_begin( vec.data() ),
m_end( m_begin + vec.size() )
{
m_state = std::unique_ptr<void, void(*)(void*)>(
new std::vector<T,A>( std::move(vec) ),
[](void* ptr){
delete static_cast<std::vector<T,A>*>(ptr);
}
);
}
buffer(buffer&&)=default;
buffer& operator=(buffer&&)=default;
~buffer() = default;
T* begin() const { return m_begin; }
T* end() const { return m_end; }
std::size_t size() const { return begin()==end(); }
bool empty() const { return size()==0; }
T* data() const { return m_begin; }
T& operator[](std::size_t i) const {
return data()[i];
}
explicit operator bool() const { return (bool)m_state; }
template<class U>
using is_buffer_compatible = std::integral_constant<bool,
sizeof(U)==sizeof(T)
&& alignof(U)==alignof(T)
&& !std::is_pointer<T>{}
>;
template<class U,
std::enable_if_t< is_buffer_compatible<U>{}, bool > = true
>
buffer reinterpret( buffer<U> o ) {
return {std::move(o.m_state), reinterpret_cast<T*>(o.m_begin()),reinterpret_cast<T*>(o.m_end())};
}
private:
buffer(std::unique_ptr<void, void(*)(void*)> state, T* b, T* e):
m_state(std::move(state)),
m_begin(begin),
m_end(end)
{}
std::unique_ptr<void, void(*)(void*)> m_state;
T* m_begin = 0;
T* m_end = 0;
};
live example: this type erases a buffer of T.
template<class T>
struct raster {
buffer<T> v;
template<typename U, typename = std::enable_if_t<sizeof(T) == sizeof(U)>>
raster(raster<U>&& other):
v( buffer<T>::reinterpret( std::move(other).v ) )
{}
};
note that my buffer has a memory allocation in it; compared to millions of elements that is cheap. It is also move-only.
The memory allocation can be eliminated through careful use of the small buffer optimization.
I'd leave it move-only (who wants to accidentally copy a million elements?) and maybe write
buffer clone() const;
which creates a new buffer with the same contents.
Note that instead of a const buffer<int> you should use a buffer<const int> under the above design. You can change that by duplicating the begin() const methods to have const and non-const versions.
This solution relies on your belief that reinterpreting a buffer of int32_ts as a buffer of uint32_ts (or vice versa) doesn't mess with anything. Your compiler may provide this guarantee, but the C++ standard does not.
The problem is that the implementation of the vector template itself may be specialised for the type. For some weird whacky reason we don't understand today, the top-level vector might need an extra member not provided in vector, so simply reinterpret casting will not work safely.
Another evil approach might be to look at the Allocator of your two vectors.
If this was
a custom allocator you had written and both were a derived class from vector
and you wrote an overload in the class for swap and =&&
within which you created wrapped tmp vectors to swap with
and detected that the Allocator was being called within those temporary objects constructors/destructors, and on the same thread
to release and reallocate the same sized array
Then, perhaps you could legitimately pass the memory buffer between them without resetting the content.
But that is a lot of fragile work!
Consider that I have a std::vector.
std::vector<int> blah;
blah.push_back(1);
blah.push_back(2);
I now want to pass the vector somewhere and disallow modifying the contents of the objects its contains while still allowing to modify the container when able:
// Acceptable use:
void call_something() {
std::vector<int> blah;
blah.push_back(1);
blah.push_back(2);
// Currently, compiler error because of mismatching types
something(blah);
}
void something(std::vector<const int>& blah)
{
// Auto translates to 'const int'
for ( auto& i : blah ) {
// User cannot modify i.
std::cout << i << std::endl;
}
blah.push_back(blah.size()); // This should be acceptable
blah.emplace_back(); // This should be acceptable
return;
}
// Unacceptable use:
void something_else(const std::vector<int>& blah)
{
// Because of const vector, auto translates to 'const int'
for ( auto& i : blah ) {
std::cout << i std::endl;
}
blah.push_back(blah.size()); // This will present an unacceptable compiler error.
blah.emplace_back(); // This will present an unacceptable compiler error.
return;
}
Is there an easy way to do this?
To enable the operations you wish to allow while preventing the others, you need to take a fine-grained approach to your function's interface. For example, if your calling code were to pass const iterators (begin and end) as well as a back inserter (or custom back emplacer functor), then exactly the subset of operations you showed would be possible.
template <class Iter, class F>
void something(Iter begin, Iter end, F&& append)
{
using value_type = typename std::iterator_traits<Iter>::value_type;
std::copy(begin, end, std::ostream_iterator<value_type>(std::cout, "\n"));
append(std::distance(begin, end));
append();
return;
}
That said I don't find your examples particularly compelling. Do you have a real scenario in which you must maintain mutable elements, pass a mutable container to a function, yet treat the passed elements as immutable?
There is no easy way to do this. One way would be to wrap a vector in a type that exposes only the functionality that you want to allow. For instance
template<typename T, typename A = std::allocator<T>>
struct vector_wrap
{
using iterator = typename std::vector<T, A>::const_iterator;
using const_iterator = typename std::vector<T, A>::const_iterator;
using size_type = typename std::vector<T, A>::size_type;
vector_wrap(std::vector<T, A>& vec)
: vec_(&vec)
{}
void push_back(T const& value) { vec_->push_back(value); }
void push_back(T&& value) { vec_->push_back(std::move(value)); }
size_type size() { return vec_->size(); }
iterator begin() const { return vec_->cbegin(); }
iterator end() const { return vec_->cend(); }
private:
std::vector<T, A> *vec_;
};
Since the above implementation only stores a pointer to the vector it wraps, you'll have to ensure that the lifetime of the vector is longer than that of vector_wrap.
You'll have to modify something and something_else so that they take a vector_wrap<int> as argument. Since vector_wrap::begin and vector_wrap::end return const_iterators, you'll not be allowed to modify existing elements within the for statement.
Live demo
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
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