I want to write a wrapper over the STL containers like map, vector, unordered map etc. that do not have copy or move constructors. There are a few approaches that I can think of, but none are nice:
Approach 1: Use templates:
// Define a template NoCopyMove which can be instantiated with STL container types.
template <typename V>
struct NoCopyMove {
public:
using value_type = V;
value_type& get() { return val_; }
template <typename... Args>
NoCopyMove(Args&&... args): val_(std::forward<Args>(args)...) {}
private:
NoCopyMove(const NoCopyMove&) = delete;
NoCopyMove(NoCopyMove&&) = delete;
value_type val_;
};
The above can be instantiated with any STL container and then the container can be accessed using the get()
function
Approach 2: Use Public Inheritance:
template <typename Key,
typename T,
typename Hash = std::hash<Key>,
typename KeyEqual = std::equal_to<Key>,
class Allocator = std::allocator<std::pair<const Key, T>>>
class unordered_map_ncm
: public std::unordered_map<Key, T, Hash, KeyEqual, Allocator> {
// Inherit constructors.
using std::unordered_map<Key, T, Hash, KeyEqual, Allocator>::unordered_map;
private:
unordered_map_ncm(const unordered_map_ncm&) = delete;
unordered_map_ncm(unordered_map_ncm&&) = delete;
};
The above is susceptible to pointer slicing because STL containers do not have virtual constructors and has to be done for each STL type. The upside is that we get to use STL-like functions without the .get()
call in the template-based approach.
Approach 2': The above can be generalized further so that instead of a map, any STL type can be used as follows:
template <template <typename...> class T, typename... Us>
class NoCopyMove : public T<Us...> {
public:
using T<Us...>::T;
private:
NoCopyMove(const NoCopyMove&) = delete;
NoCopyMove(NoCopyMove&&) = delete;
};
template<typename Key,
typename T,
typename Hash = std::hash<Key>,
typename KeyEqual = std::equal_to<Key>,
class Allocator = std::allocator<std::pair<const Key, T>>>
using unordered_map_ncm = NoCopyMove<std::unordered_map, Key, T, Hash, KeyEqual, Allocator>;
Even after this improvement, we have the drawback of pointer slicing.
I cannot think of anything which gives a friendly usage without the pointer slicing drawback. Any suggestions would be helpful.
It is better to privately inherit from the container - than to have it as private member - because we can use use Base::xxxx;
to get types and methods from it.
And of course - delete copy constructor and assign operator (move will be deleted too that way).
If we close our eyes on that we cannot prevent from copying element one by one NoCopy<std::vector<int>> v; std::vector<int> a(v.begin(), a.end());
- then this should work:
namespace detail {
template <typename Base>
struct ContainerSpecific : Base {
using Base::Base;
};
}
template <typename Container>
struct ContainerNotCopyable : private detail::ContainerSpecific<Container>
// note: this is private ^^^^^^^ inheritance:
{
using Base = detail::ContainerSpecific<Container>;
using Base::Base;
ContainerNotCopyable(const ContainerNotCopyable&) = delete;
ContainerNotCopyable& operator=(const ContainerNotCopyable&) = delete;
// common stl containers types
using Container::size_type;
using Container::value_type;
using Container::iterator;
using Container::const_iterator;
using Container::reverse_iterator;
using Container::const_reverse_iterator;
// common stl containers methods
using Container::size;
using Container::empty;
using Container::begin;
using Container::rbegin;
using Container::cbegin;
using Container::end;
using Container::cend;
using Container::rend;
};
namespace detail {
template <typename ...T>
struct ContainerSpecific<std::vector<T...>> : std::vector<T...> {
using Base = std::vector<T...>;
using Base::Base;
using Base::operator[];
using Base::at;
using Base::erase;
using Base::clear;
};
}
template <typename T, typename A=std::allocator<T>>
using VectorNotCopyable = ContainerNotCopyable<std::vector<T,A>>;
namespace detail {
template <typename T, std::size_t N>
struct ContainerSpecific<std::array<T, N>> : std::array<T, N> {
using Base = std::array<T, N>;
using Base::Base;
using Base::operator[];
using Base::at;
};
}
template <typename T, std::size_t N>
using ArrayNotCopyable = ContainerNotCopyable<std::array<T,N>>;
namespace detail {
template <typename ...T>
struct ContainerSpecific<std::map<T...>> : std::map<T...> {
using Base = std::map<T...>;
using Base::Base;
using Base::mapped_type;
using Base::key_type;
using Base::find;
using Base::emplace;
using Base::insert;
using Base::insert_or_assign;
using Base::try_emplace;
};
}
template <typename K, typename V, typename C=std::less<K>, typename A=std::allocator<std::pair<const K, V>>>
using MapNotCopyable = ContainerNotCopyable<std::map<K,V,C,A>>;
Now, we can check it works as expected:
template <typename T>
constexpr bool has_no_copy_move =
!std::is_copy_constructible_v<T>
&& !std::is_copy_assignable_v<T>
&& !std::is_move_constructible_v<T>
&& !std::is_move_assignable_v<T>;
static_assert(has_no_copy_move<VectorNotCopyable<int>>);
static_assert(has_no_copy_move<ArrayNotCopyable<int, 7>>);
static_assert(has_no_copy_move<MapNotCopyable<int, int>>);