Copy-assigning an unordered map with incompatible allocators - c++

Consider the following dummy allocator (created for the sake of example):
template<typename T> class C
{
public:
typedef T value_type;
C() = default;
template<typename U>
C(C<U> const &a)
{}
T* allocate(std::size_t n, T const* = nullptr)
{
return new T[n];
}
void deallocate(T* p, std::size_t n)
{
return;
}
typedef value_type *pointer;
typedef const value_type *const_pointer;
typedef value_type & reference;
typedef value_type const &const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
static pointer address(reference x) { return &x; }
static const_pointer address(const_reference x) { return &x; }
static size_type max_size() { return std::numeric_limits<size_type>::max(); }
template <typename U> static void destroy(U* ptr) { ptr->~U(); }
template <typename U> struct rebind { using other = C<U>; };
template<typename U, typename... Args>
static void construct(U* ptr, Args&&... args) {
new (ptr) U(std::forward<Args>(args)...);
}
};
template<class T1, class T2>
bool operator==(C<T1> const& lhs, C<T2> const& rhs)
{
return std::addressof(lhs) == std::addressof(rhs);
}
template<class T1, class T2>
bool operator!=(C<T1> const& lhs, C<T2> const& rhs)
{
return !(lhs == rhs);
}
Most of this code is boiler plate. The crucial detail is that any two instances of an allocator will be considered incompatible - bool operator== always returns false. When I try to use this allocator with most STL containers such as std::vector to copy-assign very simple elements, such as:
std::vector<int, C<int>> a;
a = std::vector<int, C<int>>();
Things work, and I get expected behavior. However, when I do the same thing, but with std::unordered_map instead, I get different behavior on the two platforms I need to support. On Linux with GCC 7.1, I continue to get expected behavior. On Windows with VS 2015, however, I get an assertion failure stating containers incompatible for swap in a VS header titled xmemory0. Note that the code used for std::unordered_map is pretty much the same as the above for std::vector:
using B = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, C<std::pair<int const, int>>>;
B b;
b = B();
Is there something inherently wrong with my allocator, and GCC 7.1 is giving me undefined behavior? If not, is this a failure with the VS 2015 runtime library? If so, why is this failure only present with unordered_map?

You can't have allocators that uniquely own state, they must be CopyConstructible. E.g. you should switch from std::unique_ptrs to std::shared_ptrs.
You should relax your comparisons
template<class T1, class T2>
bool operator==(C<T1> const& lhs, C<T2> const& rhs)
{
return /* check equality of some member of C */;
}
template<class T1, class T2>
bool operator!=(C<T1> const& lhs, C<T2> const& rhs)
{
return !(lhs == rhs);
}
You can also probably benefit from adhering to the Rule of zero/five, and defining propogate_on_container_copy_assignment, propogate_on_container_move_assignment and propogate_on_container_swap as std::true_type
A hint as to where MSVC is tripping up
Note: swapping two containers with unequal allocators if propagate_on_container_swap is false is undefined behavior.

This is not a conforming allocator. All copies of an allocator, including rebound ones, must compare equal to each other.
Additionally, unordered_map's value_type is pair<const Key, Value>, so your example should use C<pair<const int, int>>.

Related

Catch nondeterminism by error'ing on iterating pointer-keyed maps

It's been a few times we've found nondeterministic issues in the codebase I'm working on, and so far it's almost been root caused to the use of std::[unordered_]map/set<T*,U>, where the key is a pointer, combined with iteration on the map, usually in the form of a range-based for loop (since pointer values may change between executions, iteration order is nondeterministic).
I was wondering if there was some black template magic one could use to inject a static_assert when begin() is called on such a container. I think begin() is the best place to do this, or maybe iterator::operator++, since constructing iterators otherwise, such as a result of find(), is okay.
I thought I could overload std::begin, but the rules for range-based for loops state that .begin() is used if it exists. So, I'm out of ideas. Is there a clever trick to do this?
Further clarification: No custom comparator is involved, the direct value of the pointer (aka the address of the target object) is the key. This is fine for insertion and lookup, and only becomes a problem when iterating over the container since the order is based on unpredictable pointer values. I'm trying to find existing cases like this in a large existing codebase.
You can almost achieve the desired behavior with partial specializations:
20.5.4.2.1 The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.
Therefore, a simple specialization for std::map can be used to detect attempts to instantiate the template with a pointer key type:
#include <map>
namespace internal
{
// User-defined type trait
template<class Key, class T>
class DefaultAllocator
{
public:
using type = std::allocator<std::pair<const Key, T>>;
};
// Effectively the same as std::allocator, but a different type
template<class T>
class Allocator2 : public std::allocator<T> {};
}
namespace std
{
// Specialization for std::map with a pointer key type and the default allocator.
// The class inherits most of the implementation from
// std::map<Key*, T, Compare, ::internal::Allocator2<std::pair<Key*, T>>>
// to mimic the standard implementation.
template<class Key, class T, class Compare>
class map<Key*, T, Compare, typename ::internal::DefaultAllocator<Key*, T>::type> :
public map<Key*, T, Compare, ::internal::Allocator2<std::pair<Key*, T>>>
{
using base = map<Key*, T, Compare, ::internal::Allocator2<std::pair<Key*, T>>>;
using base::iterator;
using base::const_iterator;
public:
// Overload begin() and cbegin()
iterator begin() noexcept
{
static_assert(false, "OH NOES, A POINTER");
}
const_iterator begin() const noexcept
{
static_assert(false, "OH NOES, A POINTER");
}
const_iterator cbegin() const noexcept
{
static_assert(false, "OH NOES, A POINTER");
}
};
}
int main()
{
std::map<int, int> m1;
std::map<int*, int> m2;
// OK, not a specialization
m1[0] = 42;
for (auto& keyval : m1)
{
(void)keyval;
}
m2[nullptr] = 42; // Insertion is OK
for (auto& keyval : m2) // static_assert failure
{
(void)keyval;
}
}
However,
I haven't figured out a way to extend this for custom allocators: the declaration of the specialization has to depend on some user-defined type.
This is a terrible kludge, so I would only use it to find existing cases (rather than keeping as a static checker).
One approach to achieve a compile time failure for designated pointer types is to delete std::less, std::greater, std::hash, etc specializations for the specific pointer types that are susceptible to non-deterministic behavior (i.e. returned by interfaces). There are many options to provide "safe" functionality for pointer collections.
The following is a comprehensive example:
#include <cassert>
#include <memory>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#define DISABLE_NUMERIC_POINTER_SPECIALIZATIONS(T) \
namespace std { \
template <> struct hash<const T*> { std::size_t operator()(const T* obj) const = delete; }; \
template <> struct hash<T*> { std::size_t operator()(T* obj) const = delete; }; \
template <> struct less<const T*> { bool operator()(const T* lhs, const T* rhs) const = delete; }; \
template <> struct less<T*> { bool operator()(T* lhs, T* rhs) const = delete; }; \
template <> struct greater<const T*> { bool operator()(const T* lhs, const T* rhs) const = delete; }; \
template <> struct greater<T*> { bool operator()(T* lhs, T* rhs) const = delete; }; \
template <> struct less_equal<const T*> { bool operator()(const T* lhs, const T* rhs) const = delete; }; \
template <> struct less_equal<T*> { bool operator()(T* lhs, T* rhs) const = delete; }; \
template <> struct greater_equal<const T*> { bool operator()(const T* lhs, const T* rhs) const = delete; }; \
template <> struct greater_equal<T*> { bool operator()(T* lhs, T* rhs) const = delete; }; \
}
namespace NS {
class C {
public:
explicit C(int id) : m_id{id} {}
int id() const { return m_id; }
private:
int m_id;
};
inline bool operator ==(const C& lhs, const C& rhs) { return lhs.id() == rhs.id(); }
inline bool operator <(const C& lhs, const C& rhs) { return lhs.id() < rhs.id(); }
} // namespace NS
namespace std {
template <> struct hash<NS::C> { std::size_t operator()(const NS::C& obj) const { return obj.id(); } };
}
DISABLE_NUMERIC_POINTER_SPECIALIZATIONS(NS::C)
struct IndirectEqual {
template <typename T>
bool operator()(const T* lhs, const T* rhs) const {
return (lhs && rhs) ? *lhs == *rhs : lhs == rhs;
}
};
struct IndirectLess {
template <typename T>
bool operator()(const T* lhs, const T* rhs) const {
return (lhs && rhs) ? *lhs < *rhs : lhs < rhs;
}
};
struct IndirectGreater {
template <typename T>
bool operator()(const T* lhs, const T* rhs) const {
return (lhs && rhs) ? *lhs > *rhs : lhs > rhs;
}
};
struct IndirectHash {
template <typename T>
std::size_t operator()(const T* ptr) const {
return ptr ? std::hash<T>{}(*ptr) : std::numeric_limits<std::size_t>::max();
}
};
struct BuiltinLess {
template <typename T>
bool operator()(const T& lhs, const T& rhs) const { return lhs < rhs; }
};
struct SPLess {
template <typename T>
bool operator()(const std::shared_ptr<T>& lhs, const std::shared_ptr<T>& rhs) const { return lhs.get() < rhs.get(); }
};
struct BuiltinGreater {
template <typename T>
bool operator()(const T& lhs, const T& rhs) const { return lhs < rhs; };
};
struct PtrHash {
template <typename T>
std::size_t operator()(const T* ptr) const { return static_cast<std::size_t>(ptr); };
};
template <typename T>
class BasicSet : private std::set<T, BuiltinLess> {
public:
using std::set<T, BuiltinLess>::set;
using std::set<T, BuiltinLess>::find;
using std::set<T, BuiltinLess>::insert;
using std::set<T, BuiltinLess>::emplace;
using std::set<T, BuiltinLess>::end;
};
template <typename T>
class BasicSet<std::shared_ptr<T>> : private std::set<std::shared_ptr<T>, SPLess> {
public:
using std::set<std::shared_ptr<T>, SPLess>::set;
using std::set<std::shared_ptr<T>, SPLess>::find;
using std::set<std::shared_ptr<T>, SPLess>::insert;
using std::set<std::shared_ptr<T>, SPLess>::emplace;
using std::set<std::shared_ptr<T>, SPLess>::end;
};
int main()
{
// All of these decls result in a compiler error
// std::set<NS::C*> unsafe_s{new NS::C{1}, new NS::C{2}};
// std::map<NS::C*, int> unsafe_m{ {new NS::C{1}, 100} };
// std::unordered_set<NS::C*> unsafe_us{new NS::C{1}, new NS::C{2}};
// std::unordered_map<NS::C*, int> unsafe_um{ {new NS::C{1}, 123} };
std::set<NS::C*, IndirectLess> s{ new NS::C{1} };
std::unordered_set<NS::C*, IndirectHash> us1{ new NS::C{1} };
std::unordered_set<NS::C*, IndirectHash, IndirectEqual> us2{ new NS::C{1} };
auto c = new NS::C{1};
assert (s.find(c) != s.end());
assert (us1.find(c) == us1.end()); // pointers aren't equal
assert (us2.find(c) != us2.end()); // objects are equal
BasicSet<NS::C*> bs{ new NS::C{1} };
assert (bs.find(c) == bs.end()); // pointers aren't equal
auto sp1 = std::make_shared<NS::C>(10);
auto sp2 = std::make_shared<NS::C>(20);
BasicSet<std::shared_ptr<NS::C>> spset{sp1, sp2};
assert(spset.find(sp1) != spset.end());
return 0;
}
Note: This isn't perfect. E.G., one would need to disable 'volatile T*' and 'const volatile T*' variations. I'm sure there are other issues.

Custom allocator only compiles in Release mode in VS 2015

I wrote a simple dummy allocator for vector<> so that I can use vector<> as a wrapper for stack arrays, like so:
#include <vector>
#include "stdio.h"
#include "stack_allocator.h"
using namespace std;
int main() {
int buffer[100];
vector<int, StackAllocator<int>> v((StackAllocator<int>(buffer, 100)));
v.push_back(2);
printf("%d", v[0]);
v.pop_back();
}
However, only in Debug Mode in VS2015, I get the following compiler error:
'std::StackAllocator<T2, std::allocator<T>>::StackAllocator(std::StackAllocator<T, std::allocator<T>> &&)':
cannot convert argument 1 from
'std::_Wrap_alloc<std::StackAllocator<int,std::allocator<T>>>'
to
'const std::allocator<T>&'
in "c:\program files (x86)\microsoft visual studio 14.0\vc\include\xmemory0" at line 952
Compilation and execution work as intended in Release mode, though.
Here is stack_allocator.h:
#pragma once
#include <functional>
namespace std {
template <typename T, typename Allocator = allocator<T>>
class StackAllocator {
public:
typedef typename allocator_traits<Allocator>::value_type value_type;
typedef typename allocator_traits<Allocator>::pointer pointer;
typedef typename allocator_traits<Allocator>::const_pointer const_pointer;
typedef typename allocator_traits<Allocator>::size_type size_type;
typedef typename allocator_traits<Allocator>::difference_type difference_type;
typedef typename allocator_traits<Allocator>::const_void_pointer const_void_pointer;
typedef typename Allocator::reference reference;
typedef typename Allocator::const_reference const_reference;
template<typename T2>
struct rebind {
typedef StackAllocator<T2> other;
};
private:
size_t m_size;
Allocator m_allocator;
pointer m_begin;
pointer m_end;
pointer m_stack_pointer;
bool pointer_to_internal_buffer(const_pointer p) const {
return (!(less<const_pointer>()(p, m_begin)) && (less<const_pointer>()(p, m_end)));
}
public:
StackAllocator(const Allocator& alloc = Allocator()) noexcept :
m_size(0),
m_allocator(alloc),
m_begin(nullptr),
m_end(nullptr),
m_stack_pointer(nullptr) {
}
StackAllocator(pointer buffer, size_t size, const Allocator& alloc = Allocator()) noexcept :
m_size(size),
m_allocator(alloc),
m_begin(buffer),
m_end(buffer + size),
m_stack_pointer(buffer) {
}
template <typename T2>
StackAllocator(const StackAllocator<T2, Allocator>& other) noexcept :
m_size(other.m_size),
m_allocator(other.m_allocator),
m_begin(other.m_begin),
m_end(other.m_end),
m_stack_pointer(other.m_stack_pointer) {
}
pointer allocate(size_type n, const_void_pointer hint = const_void_pointer()) {
if (n <= size_type(distance(m_stack_pointer, m_end))) {
pointer result = m_stack_pointer;
m_stack_pointer += n;
return result;
}
else
return m_allocator.allocate(n, hint);
}
void deallocate(pointer p, size_type n) {
if (pointer_to_internal_buffer(p))
m_stack_pointer -= n;
else
m_allocator.deallocate(p, n);
}
size_type capacity() const noexcept {
return m_size;
}
size_type max_size() const noexcept {
return m_size;
}
pointer address(reference x) const noexcept {
if (pointer_to_internal_buffer(addressof(x)))
return addressof(x);
else
return m_allocator.address(x);
}
const_pointer address(const_reference x) const noexcept {
if (pointer_to_internal_buffer(addressof(x)))
return addressof(x);
else
return m_allocator.address(x);
}
pointer buffer() const noexcept {
return m_begin;
}
template <typename T2, typename... Args>
void construct(T2* p, Args&&... args) {
m_allocator.construct(p, forward<Args>(args)...);
}
template <typename T2>
void destroy(T2* p) {
m_allocator.destroy(p);
}
template <typename T2>
bool operator==(const StackAllocator<T2, Allocator>& other) const noexcept {
return buffer() == other.buffer();
}
template <typename T2>
bool operator!=(const StackAllocator<T2, Allocator>& other) const noexcept {
return buffer() != other.buffer();
}
};
}
Anybody has a clue as to why this error is occurring? How do I solve it?
Your rebind is broken, it should be:
template<typename T2>
struct rebind {
using Alloc2
= typename allocator_traits<Allocator>::rebind_alloc<T2>;
using other = StackAllocator<T2, Alloc2>;
};
Otherwise rebinding always creates something using std::allocator<T2> not something related to the current Allocator argument.
e.g. if you instantiate StackAllocator<int, SomeAlloc<int> and then rebind it to long you get StackAllocator<long, std::allocator<long>> which is a completely different type.
I think the VC++ debug mode is creating some kind of wrapper allocator by rebinding yours, and it fails because of your broken rebind.
Also, these lines are a problem:
typedef typename Allocator::reference reference;
typedef typename Allocator::const_reference const_reference;
Types meeting the allocator requirements don't have to have reference and const_reference so by adding these typedefs you ensure your allocator can only work with a subset of allocators. If you think you need them, just define them the same way std::allocator does:
typedef value_type& reference;
typedef const value_type& const_reference;

Should I extend std::less for a comparison functor?

I want to create a shared_ptr content-comparison functor to stand in for std::less<T> in associative containers and std algorithms. I've seen several examples of custom comparators that use the following (or similar) model:
template <typename T>
struct SharedPtrContentsLess {
bool operator()(const boost::shared_ptr<T>& lhs,
const boost::shared_ptr<T> rhs) const {
return std::less<T>(*lhs, *rhs);
//or: return (*lhs) < (*rhs);
}
//defining these here instead of using std::binary_functor (C++11 deprecated)
typedef boost::shared_ptr<T> first_argument_type;
typedef boost::shared_ptr<T> second_argument_type;
typedef bool return_type;
};
But why wouldn't I want to instead extend std::less? Like so:
template <typename T>
struct SharedPtrContentsLess : public std::less< boost:shared_ptr<T> > {
bool operator()(const boost::shared_ptr<T>& lhs,
const boost::shared_ptr<T> rhs) const {
return std::less<T>(*lhs, *rhs);
}
};
Does this buy me anything at all?
I would think this gets me the typedefs for free, as though I was extending the deprecated std::binary_function. In C++03, I actually would be extending it through std::less. However, this would also be portable from C++03 to C++11/14 and even C++17 when std::binary_function will be removed, as it just follows the changes in std::less.
I've read a bunch of answers on StackOverflow regarding std::less use, custom comparison functors, and even some of the Standard specs and proposals. I see specializations of std::less and guidance not to extend STL containers, but I can't seem to find any examples of extending std::less or guidance against it. Am I missing an obvious reason not to do this?
EDIT: Removed C++11 tag, as it is causing confusion to the answerers. I am hoping to get forward-portability, but C++03 is required. If you provide a C++11-only answer for others to use (totally fine), please note that.
You can create a reusable template towards any dereferencable object (i.e. any (smart) pointer) by simply forwarding the call to std::less or any other comparable object.
// c++11
template<template<class> Op, class T> struct deref_mixin;
template<template<class> Op, class T>
struct deref_mixin {
auto operator()(const T &l, const T &r) const
-> decltype(std::declval<Op<T>>()(*l, *r)) {
return Op<T>{}(*l, *r);
}
};
template<template<class> Op>
struct deref_mixin<Op, void> {
template<class T, class U>
auto operator()(const T &l, const U &r) const
-> decltype(std::declval<Op<T>>()(*l, *r)) {
return Op<void>{}(*l, *r);
}
};
template<class T> using less_deref = deref_mixin<std::less, T>;
template<class T> using greater_deref = deref_mixin<std::greater, T>;
template<class T> using my_comparator_deref = deref_mixin<my_comparator, T>;
// c++03
template<template<class> Op, class T>
struct deref_mixin {
bool operator()(const T &l, const T &r) const {
Op<T> op;
return op(*l, *r);
}
};
// Technically, the void template partial specialization isn't defined in c++03, but it should have been :)
template<template<class> Op>
struct deref_mixin<Op, void> {
template<class T, class U>
bool operator()(const T &l, const U &r) const {
Op<void> op;
return op(*l, *r);
}
};
template<class T> struct less_deref : deref_mixin<std::less, T> {};
As you said in your question if you inherit from std::less the you would get the three typedefs that are in std::less. What I like most about inheriting from it is it describes your intent. When I see
struct some_non_specific_name : std::less<some_type>
I know right there that this is a functor that is going to behave as an < for some_type. I don't have to read the struct body to find out anything.
As far as I can see, you are not missing any disadvantage. As you mentioned, you would automatically get the typedefs. The operator< has to be defined in both cases, and there is no difference in its implementation.
There is one thing that you might get that you might find neat, bad or just not applicable to your usecase (from here and here): there is a specialization of std::less for std::less<void> that has a template operator< deduces the return type of the operator< for the given arguments.
Unless you intend to use a SharedPtrContentsLess<void> (which probably doesn't make sense at all), both solutions would be equivalent.
I'd write a deref_less. First, my_less that smartly calls std::less:
struct my_less {
template<class Lhs, class Rhs,
class R = std::result_of_t< std::less<>( Lhs const&, Rhs const& ) >
// class R = decltype( std::declval<Lhs const&>() < std::declval<Rhs const&>() )
>
R operator()(Lhs const&lhs, Rhs const&rhs)const{
return std::less<>{}(lhs, rhs); // or lhs<rhs
}
// exact same type uses `std::less<T>`:
template<class T,
class R = std::result_of_t< std::less<>( T const&, T const& ) >
>
R operator()(T const& lhs, T const& rhs)const{
return std::less<T>{}(lhs, rhs);
}
template<class Lhs, class Rhs,
std::enable_if_t< std::is_base_of<Lhs, Rhs>{} && !std::is_same<Lhs, Rhs>{} >* = nullptr
>
bool operator()(Lhs const* lhs, Rhs const* rhs)const{
return std::less<Lhs const*>{}(lhs, rhs);
}
template<class Lhs, class Rhs,
std::enable_if_t< std::is_base_of<Rhs, Lhs>{} && !std::is_same<Lhs, Rhs>{} >* = nullptr
>
bool operator()(Lhs const* lhs, Rhs const* rhs)const{
return std::less<Rhs const*>{}(lhs, rhs);
}
template<class Lhs, class Rhs,
std::enable_if_t<
!std::is_base_of<Rhs, Lhs>{}
&& !std::is_base_of<Lhs, Rhs>{}
&& !std::is_same<Lhs, Rhs>{}
>* = nullptr
>
bool operator()(Lhs const* lhs, Rhs const* rhs)const = delete;
};
then, a deref_less that does a * then calls myless:
struct deref_less {
template<class Lhs, class Rhs,
class R = std::result_of_t< my_less( decltype(*std::declval<Lhs>()), decltype(*std::declval<Rhs>()) ) >
>
R operator()(Lhs const& lhs, Rhs const&rhs)const {
return my_less{}( *lhs, *rhs );
}
};
in C++14, but everything I used is easy to replace (std::less<> can be replaced with decltype and <s for example).
Because std::less lacks a virtual destructor (i.e. implicit destructor only), inheriting from it could technically lead to undefined behavior. Since neither type contains any data members, destruction should work no matter how the object is referenced, but the standard forbids polymorphic deletion through static destructors because it presents a strong possibility of problems (slicing, incomplete deletion) in most cases.
See this answer:
Thou shalt not inherit from std::vector

custom smart pointers and polynomial number of comparison operators

Lets say I want to implement a smart pointer a_ptr which can be compared with other smart pointers.
Then I need to implement all permutations of the comparison operators:
template<class T, class U>
bool operator==(const a_ptr<T>& a, const a_ptr<U>& b)
{
return a.get() == b.get();
}
template<class T, class U>
bool operator==(const std::shared_ptr<T>& a, const a_ptr<U>& b)
{
return a.get() == b.get();
}
template<class T, class U>
bool operator==(const a_ptr<T>& a, const std::shared_ptr<U>& b)
{
return a.get() == b.get();
}
and etc... for the rest of the operators.
Then maybe I would like to implement another smart pointer b_ptr, which would give me 6 versions for every comparison operator (since I want it to also work with a_ptr), clearly not manageable.
Is there any way to get around this problem?
EDIT:
I should have probably mentioned that I want to create wrappers around smart pointers, in which case this question makes more sense, e.g.
template<typename T>
class a_ptr
{
public:
const T* get() const {return p_.get();}
private:
std::shared_ptr<T> p_;
};
template<typename T>
class b_ptr
{
public:
const T* get() const {return p_.get();}
private:
a_ptr<T> p_;
};
If any of these, except the one comparing two a_ptr, ever hold true, your program has a bug. So, just drop it. Smart pointer is smart, because it's responsible of managing the memory behind it. And you just cannot have two different smart pointers managing one piece of memory.
Consider unique_ptr and shared_ptr. One destroys the pointer as soon as the owning smart pointer is destroyed. Second destroys the pointer only when all owning smart pointers are destroyed. I think it's fairly obvious it would quickly lead to double deletes and other fun stuff.
Ben Voigt is on the right track -- except of course we don't have concepts.
template<
typename Lhs
, typename Rhs
, typename = typename std::enable_if<
/* magic */
>::type
>
bool
operator==(Lhs const& lhs, Rhs const& rhs)
{
return lhs.get() == rhs.get();
}
This must be in the same namespace of a_ptr and b_ptr for ADL to kick (so if they are in separate namespaces you need one version of each operator for each namespace).
There are several possibilities for the magic trait to work. The one that is conceptually the simplest is to have a trait that is specialized for each pointer type you care about:
template<typename T>
struct is_smart_pointer: std::false_type {};
template<typename T, typename D>
struct is_smart_pointer<std::unique_ptr<T, D>>: std::true_type {};
// and so on...
A more elaborate trait would check that the type supports a get member. Actually, we don't need a trait for that!
template<typename Lhs, typename Rhs>
auto operator==(Lhs const& lhs, Rhs const& rhs)
-> decltype( lhs.get() == rhs.get() )
{
return lhs.get() == rhs.get();
}
This simple operator will be picked up by ADL, except that it will SFINAE itself out if one of the type doesn't support get, or if the comparison doesn't work.
How about:
template<typename T> struct you_need_a_pointer_to_do_that{} unwrap_ptr(const T&);
template<typename U> U* unwrap_ptr(U* p) { return p; }
template<typename U> U* unwrap_ptr(const a_ptr<U>& p) { return p.get(); }
template<typename U> U* unwrap_ptr(const unique_ptr<U>& p) { return p.get(); }
...
template<typename T, typename PU> auto operator<(const a_ptr<T>& a, const PU& b) -> decltype(unwrap_ptr(a) < unwrap_ptr(b)) { return unwrap_ptr(a) < unwrap_ptr(b); }
The decltype bit rejects non-pointers due to SFINAE. And the unwrap_ptr helper homogenizes the syntax for pointer access, with complexity only linear in the number of pointer types.
You can just tag your smart pointer types and then implement a generic version which relies on at least one of the passed arguments to be tagged:
template <typename T> struct is_my_smartpointer: std::false_type {};
template <typename T> struct is_my_smartpointer<a_ptr<T> >: std::true_type {};
template <typename S, typename T>
typename std::enable_if<is_my_smartpointer<S>::value || is_my_smartpointer<T>::value, bool>::type
operator== (S const& s, T const& t) {
return s.get() == t.get();
}
You could inherit your custom smart pointer from an existing smart pointer. This way you won't have to implement any operators at all.
Also as as Cat Plus Plus said. You cannot have two smart pointer managing one piece of memory. So If you're implementing a smart pointer you should make sure that holds true. You should make operator= private so it cannot be copied into another location.
It can be as difficult as writing a sfinae trait for smart pointers. As soon as you get it, the solution is pretty straight forward, you don't need c++11 for this:
#include <memory>
#include <type_traits>
#include <iostream>
namespace my {
template<class T>
class a_ptr
{
T* t_;
public:
typedef T element_type;
element_type* get() const throw() { return t_; }
};
template<class T> class is_smart_ptr; // sfinae test for T::get()?
template<class T, class U>
typename std::enable_if<
is_smart_ptr<T>::value &&
is_smart_ptr<U>::value,
bool
>::type
operator==(const T& a, const U& b)
{
return a.get() == b.get();
}
}
int main()
{
my::a_ptr<int> a;
std::shared_ptr<int> b;
a == b;
}
And since std::shared_ptr already have operator==, you need to put your comparison operator in the same namespace where your smart pointer type is.

Why doesn't this C++ STL allocator allocate?

I'm trying to write a custom STL allocator that is derived from std::allocator, but somehow all calls to allocate() go to the base class. I have narrowed it down to this code:
template <typename T> class a : public std::allocator<T> {
public:
T* allocate(size_t n, const void* hint = 0) const {
cout << "yo!";
return 0;
}
};
int main()
{
vector<int, a<int>> v(1000, 42);
return 0;
}
I expect "Yo!" to get printed, followed by some horrible error because I don't actually allocate anything. Instead, the program runs fine and prints nothing. What am I doing wrong?
I get the same results in gcc and VS2008.
You will need to provide a rebind member template and the other stuff that is listed in the allocator requirements in the C++ Standard. For example, you need a template copy constructor which accepts not only allocator<T> but also allocator<U>. For example, one code might do, which a std::list for example is likely to do
template<typename Allocator>
void alloc1chunk(Allocator const& alloc) {
typename Allocator::template rebind<
wrapper<typename Allocator::value_type>
>::other ot(alloc);
// ...
}
The code will fail if there either exist no correct rebind template, or there exist no corresponding copy constructor. You will get nowhere useful with guessing what the requirements are. Sooner or later you will have to do with code that relies on one part of those allocator requirements, and the code will fail because your allocator violates them. I recommend you take a look at them in some working draft your your copy of the Standard in 20.1.5.
In this case, the problem is that I didn't override the rebind member of the allocator. This version works (in VS2008):
template <typename T> class a : public std::allocator<T> {
public:
T* allocate(size_t n, const void* hint = 0) const {
cout << "yo!";
return 0;
}
template <typename U> struct rebind
{
typedef a<U> other;
};
};
int main() {
vector<int, a<int>> v(1000, 42);
return 0;
}
I found this by debugging through the STL headers.
Whether this works or not will be completely dependent on the STL implementation though, so I think that ultimately, Klaim is right in that this shouldn't be done this way.
I have two templates for creating customized allocators; the first works automagically if it is used on a custom type:
template<>
class std::allocator<MY_TYPE>
{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef MY_TYPE* pointer;
typedef const MY_TYPE* const_pointer;
typedef MY_TYPE& reference;
typedef const MY_TYPE& const_reference;
typedef MY_TYPE value_type;
template <class U>
struct rebind
{
typedef std::allocator<U> other;
};
pointer allocate(size_type n, std::allocator<void>::const_pointer hint = 0)
{
return reinterpret_cast<pointer>(ALLOC_FUNC(n * sizeof(T)));
}
void construct(pointer p, const_reference val)
{
::new(p) T(val);
}
void destroy(pointer p)
{
p->~T();
}
void deallocate(pointer p, size_type n)
{
FREE_FUNC(p);
}
size_type max_size() const throw()
{
// return ~size_type(0); -- Error, fixed according to Constantin's comment
return std::numeric_limits<size_t>::max()/sizeof(MY_TYPE);
}
};
The second is used when we want to have our own allocator for a predefined type with a standard allocator, for instance char, wchar_t, std::string, etc.:
namespace MY_NAMESPACE
{
template <class T> class allocator;
// specialize for void:
template <>
class allocator<void>
{
public:
typedef void* pointer;
typedef const void* const_pointer;
// reference to void members are impossible.
typedef void value_type;
template <class U>
struct rebind
{
typedef allocator<U> other;
};
};
template <class T>
class allocator
{
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
template <class U>
struct rebind
{
typedef allocator<U> other;
};
allocator() throw()
{
}
template <class U>
allocator(const allocator<U>& u) throw()
{
}
~allocator() throw()
{
}
pointer address(reference r) const
{
return &r;
}
const_pointer address(const_reference r) const
{
return &r;
}
size_type max_size() const throw()
{
// return ~size_type(0); -- Error, fixed according to Constantin's comment
return std::numeric_limits<size_t>::max()/sizeof(T);
}
pointer allocate(size_type n, allocator<void>::const_pointer hint = 0)
{
return reinterpret_cast<pointer>(ALLOC_FUNC(n * sizeof(T)));
}
void deallocate(pointer p, size_type n)
{
FREE_FUNC(p);
}
void construct(pointer p, const_reference val)
{
::new(p) T(val);
}
void destroy(pointer p)
{
p->~T();
}
};
template <class T1, class T2>
inline
bool operator==(const allocator<T1>& a1, const allocator<T2>& a2) throw()
{
return true;
}
template <class T1, class T2>
inline
bool operator!=(const allocator<T1>& a1, const allocator<T2>& a2) throw()
{
return false;
}
}
The first template above, for your own defined type, does not require any further handling but is used automatically by the standard container classes. The second template requires further work when used on a standard type. For std::string, for example, one have to use the following construct when declaring variables of that type (it is simplest with a typedef):
std::basic_string<char>, std::char_traits<char>, MY_NAMESPACE::allocator<char> >
The following code prints "yo" as expected - what you were seeing was our old friend "undefined behaviour".
#include <iostream>
#include <vector>
using namespace std;
template <typename T> class a : public std::allocator<T> {
public:
T* allocate(size_t n, const void* hint = 0) const {
cout << "yo!";
return new T[10000];
}
};
int main()
{
vector<int, a<int> > v(1000, 42);
return 0;
}
Edit: I just checked out the C++ Standard regarding the default allocator. There is no prohibition on inheriting from it. In fact, as far as I'm aware, there is no such prohibition in any part of the Standard.