I have been looking online for a way to do a perfect and efficient redirection (proxy) to a member object. But I can't find any useful resource.
I imagine this is a task than programmers are doing often (sometimes badly), and it could be a good knowledge to know a good, efficient and concise (minimalist) way to do it.
I would like your advice to know if my understanding of move operations is right and if this proxy is correct particularly for copy and move constructor / assignment operators.
To do a perfect forwarding in a move constructor / assignment operator my understanding is that we use std::move if we are sure we pass an lvalue to the encapsulated object, and std::forward otherwise.
Is it correct ?
Here is my code
#include <boost/variant.hpp>
/* ********************************************************************/
// Class
/* ********************************************************************/
template <class... T>
class BoostVariantWrapper {
/* ********************************************************************/
// Private fields
/* ********************************************************************/
boost::variant<T...> variant;
public:
/* ********************************************************************/
// Constructors / Destructors
/* ********************************************************************/
BoostVariantWrapper() {}
BoostVariantWrapper(const BoostVariantWrapper &other) :
BoostVariantWrapper(other.variant)
{}
BoostVariantWrapper(BoostVariantWrapper &other) :
BoostVariantWrapper(other.variant)
{}
BoostVariantWrapper(BoostVariantWrapper &&other) :
BoostVariantWrapper(std::move(other.variant))
{}
template<class TOther>
BoostVariantWrapper(TOther &&other) :
variant(std::forward<TOther>(other))
{}
/* ********************************************************************/
// Public methods
/* ********************************************************************/
template <class U>
U& get() {
return boost::get<U>(variant);
}
template <class Fn>
inline void applyVisitor(Fn&& visitor) {
boost::apply_visitor(std::forward<Fn>(visitor), variant);
}
template <class Fn>
inline void applyVisitor(Fn&& visitor) const {
boost::apply_visitor(std::forward<Fn>(visitor), variant);
}
/* ********************************************************************/
// Operators
/* ********************************************************************/
BoostVariantWrapper& operator=(const BoostVariantWrapper &other) {
return operator=(other.variant);
}
BoostVariantWrapper& operator=(BoostVariantWrapper &other) {
return operator=(other.variant);
}
BoostVariantWrapper& operator=(BoostVariantWrapper &&other) {
return operator=(std::move(other.variant));
}
template<class TOther>
BoostVariantWrapper& operator=(TOther &&other) {
variant = std::forward<TOther>(other);
return *this;
}
bool operator==(const BoostVariantWrapper &other) const {
return variant == other.variant;
}
bool operator<(const BoostVariantWrapper &other) const {
return variant < other.variant;
}
friend std::ostream& operator<<(std::ostream &os, const BoostVariantWrapper &x) {
os << x.variant;
return os;
}
/* ********************************************************************/
// Serialization
/* ********************************************************************/
template<class Archive>
void serialize(Archive &ar, const unsigned int) {
ar & variant;
}
};
Edited based on comment of Jarod42
std::forward by itself does perfect forwarding. It is meant for this purpose.
Related
I am attempting to implement a simplified shared_ptr that does not include weak_ptr functionality. It does support make_shared to perform only a single application (in fact, this is the only way to create a new shared_ptr in my implementation).
The problem is, something about my implementation causes my code to crash (hard fault on an ARM MCU).
/**
* #file shared_ptr.h
*
*/
#ifndef TW_SHARED_PTR_H_
#define TW_SHARED_PTR_H_
//==============================================================================
// INCLUDES
//==============================================================================
#include "_common.h"
/*
* If TW_CONFIG_USE_STD_SHARED_PTR == 1, simply define tw::shared_ptr (and other
* helper classes/methods) as wrappers around std::shared_ptr
*/
#if TW_CONFIG_USE_STD_SHARED_PTR == 1
#include <memory>
namespace tw {
template <typename T>
using shared_ptr = std::shared_ptr<T>;
template <typename T>
using enable_shared_from_this = std::enable_shared_from_this<T>;
template <typename T, typename ... ARGS>
inline shared_ptr<T> make_shared(ARGS&&... args) {
return std::make_shared<T>(std::forward<ARGS>(args)...);
}
} //namespace tw
#else //TW_CONFIG_USE_STD_SHARED_PTR == 0
#include <atomic>
#include <type_traits>
//==============================================================================
// DEFINES
//==============================================================================
#ifndef TW_SHARED_PTR_ASSERT
#include <cstdlib>
#define TW_SHARED_PTR_ASSERT(x_) if (!(x_)) abort()
#endif
namespace tw {
//==============================================================================
// CLASSES
//==============================================================================
template <typename T>
class shared_ptr;
class _enable_shared_from_this;
template <typename T>
class enable_shared_from_this;
//-----[ CLASS: _shared_ptr_base ]-------------------------------------------------
class _shared_ptr_base {
template <typename T_, typename ... ARGS_>
friend
shared_ptr<T_> make_shared(ARGS_&&...);
friend class _enable_shared_from_this;
protected:
using ref_count_type = std::atomic<unsigned int>;
struct control_block {
ref_count_type refCount;
struct {
const void* p;
void(*destroy)(const void*);
inline void operator()(){ destroy(p); }
inline operator bool() { return (p != nullptr) && (destroy != nullptr); }
} destructor;
};
};
//-----[ TEMPLATE CLASS: shared_ptr<T> ]-----------------------------------------
template <typename T>
class shared_ptr : public _shared_ptr_base {
template <typename T_>
friend
class shared_ptr;
template <typename T_>
friend
class enable_shared_from_this;
public:
using value_type = T;
public:
shared_ptr():
_block(nullptr),
_ptr(nullptr)
{
}
~shared_ptr() {
reset();
}
//Converting copy constructor
template <typename U_>
shared_ptr(const shared_ptr<U_>& other):
_block(other._block),
_ptr(other._ptr)
{
if (_block != nullptr) {
TW_SHARED_PTR_ASSERT(_ptr != nullptr);
//Increment ref count
++(_block->refCount);
}
}
//Converting move constructor
template <typename U_>
shared_ptr(shared_ptr<U_>&& other):
_block(other._block),
_ptr(other._ptr)
{
other._block = nullptr;
other._ptr = nullptr;
}
public:
//Converting copy assignment operator
template <typename U_>
shared_ptr& operator=(const shared_ptr<U_>& rhs) {
if (static_cast<void*>(this) != static_cast<void*>(&rhs)) {
reset();
_block = rhs._block;
_ptr = rhs._ptr;
if (_block != nullptr) {
TW_SHARED_PTR_ASSERT(_ptr != nullptr);
//Increment ref count
++(_block->refCount);
}
}
return *this;
}
//Converting move assignment operator
template <typename U_>
shared_ptr& operator=(shared_ptr<U_>&& rhs) {
if (static_cast<void*>(this) != static_cast<void*>(&rhs)) {
reset();
_block = rhs._block;
_ptr = rhs._ptr;
rhs._block = nullptr;
rhs._ptr = nullptr;
}
return *this;
}
inline T* operator->() const noexcept {
TW_SHARED_PTR_ASSERT(_ptr != nullptr);
return _ptr;
}
inline operator T*() const noexcept { return _ptr; }
inline T& operator*() const noexcept {
TW_SHARED_PTR_ASSERT(_ptr != nullptr);
return *_ptr;
}
inline operator bool() const noexcept { return (_ptr != nullptr); }
public:
inline T* get() const noexcept { return _ptr; }
void reset() {
if (_block != nullptr) {
TW_SHARED_PTR_ASSERT(_ptr != nullptr);
if (--(_block->refCount) == 0) {
TW_SHARED_PTR_ASSERT(_block->destructor);
//Free resources by calling deleter
_block->destructor();
}
}
_block = nullptr;
_ptr = nullptr;
}
inline ref_count_type::value_type use_count() const noexcept {
return (_block == nullptr) ? 0 : _block->refCount.load();
}
protected:
shared_ptr(control_block* block, value_type* ptr):
_block(block),
_ptr(ptr)
{
if (_block != nullptr) {
TW_SHARED_PTR_ASSERT(_ptr != nullptr);
//Increment ref count
++(_block->refCount);
}
}
private:
control_block* _block;
value_type* _ptr;
template <typename T_, typename ... ARGS_>
friend
shared_ptr<T_> make_shared(ARGS_&&...);
};
//-----[ CLASS: _enable_shared_from_this ]--------------------------------------
class _enable_shared_from_this {
protected:
_enable_shared_from_this():
_block(nullptr)
{
}
protected:
_shared_ptr_base::control_block* _block;
};
//-----[ TEMPLATE CLASS: enable_shared_from_this ]------------------------------
template <typename T>
class enable_shared_from_this : public _enable_shared_from_this {
template <typename T_, typename ... ARGS_>
friend
shared_ptr<T_> make_shared(ARGS_&&...);
public:
shared_ptr<T> shared_from_this() noexcept {
return shared_ptr<T>(_block, static_cast<T*>(this));
}
shared_ptr<const T> shared_from_this() const noexcept {
return shared_ptr<const T>(_block, static_cast<const T*>(this));
}
};
//==============================================================================
// FUNCTIONS
//==============================================================================
template <typename T, typename ... ARGS>
shared_ptr<T> make_shared(ARGS&&... args) {
struct FullBlock {
_shared_ptr_base::control_block block;
T value;
};
//Allocate block on heap
auto block = new FullBlock{
{0},
T{std::forward<ARGS>(args)...} //value
};
block->block.destructor.p = block;
block->block.destructor.destroy = [](const void* x){
delete static_cast<const FullBlock*>(x);
};
if constexpr (std::is_base_of_v<_enable_shared_from_this, T>) {
block->value._block = &block->block;
}
/*
* Up until this point, the make_shared function "owns" the pointer to
* 'block'. It now "transfers" ownership of this pointer to a shared_ptr
* instance.
*/
return shared_ptr<T>(&block->block, &block->value);
}
} //namespace tw
#endif
#endif /* TW_SHARED_PTR_H_ */
The define TW_CONFIG_USE_STD_SHARED_PTR allows me to swap between my shared_ptr implementation and the standard library's implementation. The standard library's implementation does NOT cause a hard fault in my application so I know that there must be something wrong with my implementation.
Can anyone spot any obvious problems in my implementation?
Even though this question was a bit open ended without an straight forward answer, those who commented did help me out quite a bit. #WhozCraig pointed out that there was not copy or move constructors. I did not realize this was the case as I thought that my "converting" (i.e., templated) copy and move constructors would also cover the default case (same pointer). However, I guess this is NOT how the C++ compiler handles this.
So, for reference, here is a version of my pointer which works correctly:
/**
* #file shared_ptr.h
*
*/
#ifndef TW_SHARED_PTR_H_
#define TW_SHARED_PTR_H_
//==============================================================================
// INCLUDES
//==============================================================================
/*
* If TW_CONFIG_USE_STD_SHARED_PTR == 1, simply define tw::shared_ptr (and other
* helper classes/methods) as wrappers around std::shared_ptr
*/
#if TW_CONFIG_USE_STD_SHARED_PTR == 1
#include <memory>
namespace tw {
template <typename T>
using shared_ptr = std::shared_ptr<T>;
template <typename T>
using enable_shared_from_this = std::enable_shared_from_this<T>;
template <typename T, typename ... ARGS>
inline shared_ptr<T> make_shared(ARGS&&... args) {
return std::make_shared<T>(std::forward<ARGS>(args)...);
}
} //namespace tw
#else //TW_CONFIG_USE_STD_SHARED_PTR == 0
#include <atomic>
#include <type_traits>
//==============================================================================
// DEFINES
//==============================================================================
#ifndef TW_SHARED_PTR_ASSERT
#include <cstdlib>
#define TW_SHARED_PTR_ASSERT(x_) if (!(x_)) abort()
#endif
namespace tw {
//==============================================================================
// CLASSES
//==============================================================================
template <typename T>
class shared_ptr;
class _enable_shared_from_this;
template <typename T>
class enable_shared_from_this;
//-----[ CLASS: _shared_ptr_base ]-------------------------------------------------
class _shared_ptr_base {
template <typename T_, typename ... ARGS_>
friend
shared_ptr<T_> make_shared(ARGS_&&...);
friend class _enable_shared_from_this;
protected:
using ref_count_type = std::atomic<unsigned int>;
struct control_block {
ref_count_type refCount;
struct {
const void* p;
void(*destroy)(const void*);
inline void operator()(){ destroy(p); }
inline operator bool() { return (p != nullptr) && (destroy != nullptr); }
} destructor;
};
};
//-----[ TEMPLATE CLASS: shared_ptr<T> ]-----------------------------------------
template <typename T>
class shared_ptr : public _shared_ptr_base {
template <typename T_>
friend
class shared_ptr;
template <typename T_>
friend
class enable_shared_from_this;
public:
using value_type = T;
public:
/**
* #brief Default constructor
*
* By default, a shared_ptr is null (nullptr)
*/
shared_ptr():
_block(nullptr),
_ptr(nullptr)
{
}
/**
* #brief Destructor
*/
~shared_ptr() {
reset();
}
/**
* #brief Copy constructor
*/
shared_ptr(const shared_ptr& other):
_block(other._block),
_ptr(other._ptr)
{
_incr();
}
/**
* #brief Converting copy constructor
*/
template <typename U_>
shared_ptr(const shared_ptr<U_>& other):
_block(other._block),
_ptr(other._ptr)
{
_incr();
}
/**
* #brief Move constructor
*/
shared_ptr(shared_ptr&& other):
_block(other._block),
_ptr(other._ptr)
{
other._block = nullptr;
other._ptr = nullptr;
}
/**
* #brief Converting move constructor
*/
template <typename U_>
shared_ptr(shared_ptr<U_>&& other):
_block(other._block),
_ptr(other._ptr)
{
other._block = nullptr;
other._ptr = nullptr;
}
public:
/**
* #brief Copy assignment operator
*/
shared_ptr& operator=(const shared_ptr& rhs) {
if (static_cast<void*>(this) != static_cast<void*>(&rhs)) {
reset();
_block = rhs._block;
_ptr = rhs._ptr;
_incr();
}
return *this;
}
/**
* #brief Converting copy assignment operator
*/
template <typename U_>
shared_ptr& operator=(const shared_ptr<U_>& rhs) {
if (static_cast<void*>(this) != static_cast<void*>(&rhs)) {
reset();
_block = rhs._block;
_ptr = rhs._ptr;
_incr();
}
return *this;
}
/**
* #brief Move assignment operator
*/
shared_ptr& operator=(shared_ptr&& rhs) {
if (static_cast<void*>(this) != static_cast<void*>(&rhs)) {
reset();
_block = rhs._block;
_ptr = rhs._ptr;
rhs._block = nullptr;
rhs._ptr = nullptr;
}
return *this;
}
/**
* #brief Converting move assignment operator
*/
template <typename U_>
shared_ptr& operator=(shared_ptr<U_>&& rhs) {
if (static_cast<void*>(this) != static_cast<void*>(&rhs)) {
reset();
_block = rhs._block;
_ptr = rhs._ptr;
rhs._block = nullptr;
rhs._ptr = nullptr;
}
return *this;
}
inline T* operator->() const noexcept {
TW_SHARED_PTR_ASSERT(_ptr != nullptr);
return _ptr;
}
inline operator T*() const noexcept { return _ptr; }
inline T& operator*() const noexcept {
TW_SHARED_PTR_ASSERT(_ptr != nullptr);
return *_ptr;
}
inline operator bool() const noexcept { return (_ptr != nullptr); }
public:
inline T* get() const noexcept { return _ptr; }
void reset() {
if (_block != nullptr) {
TW_SHARED_PTR_ASSERT(_ptr != nullptr);
if (--(_block->refCount) == 0) {
TW_SHARED_PTR_ASSERT(_block->destructor);
//Free resources by calling deleter
_block->destructor();
}
}
_block = nullptr;
_ptr = nullptr;
}
inline ref_count_type::value_type use_count() const noexcept {
return (_block == nullptr) ? 0 : _block->refCount.load();
}
protected:
shared_ptr(control_block* block, value_type* ptr):
_block(block),
_ptr(ptr)
{
if (_block != nullptr) {
TW_SHARED_PTR_ASSERT(_ptr != nullptr);
//Increment ref count
++(_block->refCount);
}
}
private:
/**
* #brief Increment the reference count
*/
void _incr() {
if (_block != nullptr) {
TW_SHARED_PTR_ASSERT(_ptr != nullptr);
//Increment ref count
++(_block->refCount);
}
}
private:
control_block* _block;
value_type* _ptr;
template <typename T_, typename ... ARGS_>
friend
shared_ptr<T_> make_shared(ARGS_&&...);
};
//-----[ CLASS: _enable_shared_from_this ]--------------------------------------
class _enable_shared_from_this {
protected:
_enable_shared_from_this():
_block(nullptr)
{
}
protected:
_shared_ptr_base::control_block* _block;
};
//-----[ TEMPLATE CLASS: enable_shared_from_this ]------------------------------
template <typename T>
class enable_shared_from_this : public _enable_shared_from_this {
template <typename T_, typename ... ARGS_>
friend
shared_ptr<T_> make_shared(ARGS_&&...);
public:
shared_ptr<T> shared_from_this() noexcept {
return shared_ptr<T>(_block, static_cast<T*>(this));
}
shared_ptr<const T> shared_from_this() const noexcept {
return shared_ptr<const T>(_block, static_cast<const T*>(this));
}
};
//==============================================================================
// FUNCTIONS
//==============================================================================
template <typename T, typename ... ARGS>
shared_ptr<T> make_shared(ARGS&&... args) {
struct FullBlock {
_shared_ptr_base::control_block block;
T value;
};
//Allocate block on heap
auto block = new FullBlock{
{0},
T{std::forward<ARGS>(args)...} //value
};
block->block.destructor.p = block;
block->block.destructor.destroy = [](const void* x){
delete static_cast<const FullBlock*>(x);
};
if constexpr (std::is_base_of_v<_enable_shared_from_this, T>) {
block->value._block = &block->block;
}
/*
* Up until this point, the make_shared function "owns" the pointer to
* 'block'. It now "transfers" ownership of this pointer to a shared_ptr
* instance.
*/
return shared_ptr<T>(&block->block, &block->value);
}
} //namespace tw
#endif
#endif /* TW_SHARED_PTR_H_ */
Consider the following C++ code with my failed attempt to avoid preference of non-template copy&move constructors and assignment operators:
template<typename T> class A {
public:
A() { /* implementation here */ }
// Remove from the overloads the default copy&move constructors and assignment operators
A(const A&) = delete;
A& operator=(const A&) = delete;
A(A&&) = delete;
A& operator=(A&&) = delete;
// I want these to be used e.g. by std::vector
template<typename U> A(const A<U>& fellow) { /* implementation here */ }
template<typename U> A& operator=(const A<U>& fellow) { /* implementation here */ }
template<typename U> A(A<U>&& fellow) { /* implementation here */ }
template<typename U> A& operator=(A<U>&& fellow) { /* implementation here */ }
};
However, I get the following error
attempting to reference a deleted function
when trying to push A items to a vector or simply copy-construct like:
A<int> a1{};
A<int> a2(a1);
UPDATE1: I need template copy&move constructors and assignment operators, because the template argument really just controls some caching, so A<T1> can be safely assigned to A<T2>.
You can make compiler happy by declaring deleted copy constructor / assignment operator with alternative signature which will not cause this overload to be selected but will prevent generation of constructor / assignment operator by compiler:
template<typename T> class A
{ public:
A() { /* implementation here */ }
// Remove from the implicit declaration of the default copy&move constructors and assignment operators
A(A volatile const &) = delete;
A & operator =(A volatile const &) = delete;
// I want these to be used e.g. by std::vector
template<typename U> A(A<U> const & fellow) { /* implementation here */ }
template<typename U> A & operator =(A<U> const & fellow) { /* implementation here */ return *this;}
template<typename U> A(A<U> && fellow) { /* implementation here */ }
template<typename U> A & operator =(A<U> && fellow) { /* implementation here */ return *this; }
};
int main()
{
A<int> a1{};
A<int> a2{a1};
return 0;
}
online compiler
15.8.1 Copy/move constructors [class.copy.ctor]
1. A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments
A minimal example of a copy constructor that delegate the execution to the template constructor using a second unused (and defaulted) argument
#include <iostream>
template <typename T>
struct A
{
A()
{ }
A (A const & a0) : A{a0, 0}
{ }
template<typename U>
A (A<U> const &, int = 0)
{ std::cout << "template constructor" << std::endl; }
};
int main()
{
A<int> a0;
A<int> a1{a0};
}
-- EDIT --
The OP asks
What about operator=? Trying to add a dummy parameter gives compiler errors binary 'operator =' has too many parameters and 'operator =' cannot have default parameters
For operator=() I propose to "delegate" (not in the meaning of delegating constructor, in this case) both operators to a normal method; a template one.
Something as
template <typename U>
A & assign (A<U> const &)
{ /* do assignment */ return *this; }
A & operator= (A const & a0)
{ return assign(a0); }
template <typename U>
A & operator= (A<U> const & a0)
{ return assign(a0); }
Maybe the assign() method can be a private one.
Or better, as suggested by Jarod42 (thanks), directly calling the template operator from the not-template one
template <typename U>
A & operator= (A<U> const & a0)
{ /* do assignment */ return *this; }
A & operator= (A const & a0)
{ return operator=<T>(a0); }
Given the following simple C++ class:
using namespace std;
template<class T1>
class ValueWrapper {
private:
T1 value_;
public:
ValueWrapper() {}
ValueWrapper(const T1& value) {
value_ = value;
}
ValueWrapper(const ValueWrapper<T1> &wrapper) {
value_ = wrapper.value_;
}
ValueWrapper& Set(const T1& value) {
value_ = value;
return *this;
}
T1 Get() const {
return value_;
}
};
I was trying to create a simple shared_ptr wrapper for that class (ultimately allowing the developer to use the class without the dereferencing operator if desired). While I've seen a few examples of wrapping a shared_ptr, I couldn't find any that also used a specialization for a templated class.
Using the class above, I created a ValueShared class which derives from shared_ptr:
template<class T1>
class ValueShared : public shared_ptr<T1> {
public:
ValueShared& operator =(const T1& rhs) {
// nothing to do in base
return *this;
}
};
Then, I created a custom make_shared_value function:
//
// TEMPLATE FUNCTION make_shared
template<class T1, class... Types> inline
ValueShared<T1> make_shared_value(Types&&... Arguments)
{ // make a shared_ptr
_Ref_count_obj<T1> *_Rx = new _Ref_count_obj<T1>(_STD forward<Types>(Arguments)...);
ValueShared<T1> _Ret;
_Ret._Resetp0(_Rx->_Getptr(), _Rx);
return (_Ret);
}
But, here's the problem code:
template<class T1, class ValueWrapper<T1>>
class ValueShared<ValueWrapper<T1>> : public shared_ptr<ValueWrapper<T1>>{
public:
ValueShared& operator =(const ValueWrapper<T1>& rhs) {
auto self = this->get();
self.Set(rhs->Get());
return *this;
}
};
I wanted to provide a specialization of the equals operator here that was specialized to the ValueWrapper class (so that it would Get/Set the value from the right hand side value).
I've tried a few things, but the current error is:
error C2943: 'ValueWrapper<T1>' : template-class-id redefined
as a type argument of a template
Maybe this isn't the proper approach, or maybe it's not possible?
Following should remove your error:
template<class T1>
class ValueShared<ValueWrapper<T1>> : public shared_ptr<ValueWrapper<T1>> {
public:
ValueShared& operator =(const ValueWrapper<T1>& rhs)
{
auto self = this->get();
self->Set(rhs.Get());
return *this;
}
};
To narrow it down: I'm currently using Boost.Unordered. I see two possible solutions:
Define my own Equality Predicates and Hash Functions and to utilize templates (maybe is_pointer) to distinct between pointers and instances;
Simply to extend boost::hash by providing hash_value(Type* const& x) as for hashing; and add == operator overload as free function with (Type* const& x, Type* const& y) parameters as for equality checking.
I'm not sure whether both variations are actually possible, since I didn't test them. I would like to find out you handle this problem. Implementations are welcome :)
EDIT 1:
What about this?
template<class T>
struct Equals: std::binary_function<T, T, bool> {
bool operator()(T const& left, T const& right) const {
return left == right;
}
};
template<class T>
struct Equals<T*> : std::binary_function<T*, T*, bool> {
bool operator()(T* const& left, T* const& right) const {
return *left == *right;
}
};
EDIT 2:
I've just defined:
friend std::size_t hash_value(Base const& base) {
boost::hash<std::string> hash;
return hash(base.string_);
}
friend std::size_t hash_value(Base* const& base) {
return hash_value(*base);
}
And then:
Derived d1("x");
Derived d2("x");
unordered_set<Base*> set;
set.insert(&d1);
assert(set.find(&d2) == end());
Debugger says that friend std::size_t hash_value(Base* const& base) is never called (GCC 4.7). Why is that?
EDIT 3:
I found out that template <class T> std::size_t hash_value(T* const& v) in boost/functional/hash.hpp on line #215 (Boost 1.49) is Boost's specialization for pointers and it simply masks your custom implementation of hash_value such as mine in EDIT 2.
Therefore, it seems like the only way here is to create a custom Hash Functor.
For the hash function, you have a choice between specializing boost::hash (or std::hash in the newer standard) or defining a new functor class. These alternatives work equally well.
For the equality operator, you need to define a new functor, because you cannot redefine the equality operator over pointers. It's a built-in operator (defined in functional terms as bool operator==( T const *x, T const *y )) and cannot be replaced.
Both of these can be defined generically by using a templated operator() in a non-templated class.
struct indirect_equal {
template< typename X, typename Y >
bool operator() ( X const &lhs, Y const &rhs )
{ return * lhs == * rhs; }
};
Follow a similar pattern for the hasher.
Taking into consideration all edits in the original post I would like to provide complete solution which satisfies my needs:
1. Equality:
template<class T>
struct Equal: ::std::binary_function<T, T, bool> {
bool operator()(T const& left, T const& right) const {
::std::equal_to<T> equal;
return equal(left, right);
}
};
template<class T>
struct Equal<T*> : ::std::binary_function<T*, T*, bool> {
bool operator()(T* const & left, T* const & right) const {
Equal<T> equal;
return equal(*left, *right);
}
};
2. Hashing:
template<class T>
struct Hash: ::std::unary_function<T, ::std::size_t> {
::std::size_t operator()(T const & value) const {
::boost::hash<T> hash;
return hash(value);
}
};
template<class T>
struct Hash<T*> : ::std::unary_function<T*, ::std::size_t> {
::std::size_t operator()(T* const & value) const {
Hash<T> hash;
return hash(*value);
}
};
So now I can continue using Boost's hash_value and it will not get masked for pointer types by Boost's default implementation (see EDIT 3).
3. Example:
In my application I have a thin wrapper for unordered_set which now looks like that:
template<class T, class H = Hash<T>, class E = Equal<T> >
class Set {
public:
// code omitted...
bool contains(const T& element) const {
return s_.find(element) != end();
}
bool insert(const T& element) {
return s_.insert(element).second;
}
// code omitted...
private:
::boost::unordered::unordered_set<T, H, E> s_;
};
So if we have some base class:
class Base {
public:
Base(const ::std::string& string) {
if (string.empty())
throw ::std::invalid_argument("String is empty.");
string_ = string;
}
virtual ~Base() {
}
friend bool operator==(const Base& right, const Base& left) {
return typeid(right) == typeid(left) && right.string_ == left.string_;
}
friend bool operator!=(const Base& right, const Base& left) {
return !(right == left);
}
friend ::std::size_t hash_value(Base const& base) {
::boost::hash<std::string> hash;
return hash(base.string_);
}
friend ::std::size_t hash_value(Base* const& base) {
return hash_value(*base);
}
private:
::std::string string_;
};
And some derived class:
class Derived: public Base {
public:
Derived(const ::std::string& string) :
Base(string) {
}
virtual ~Derived() {
}
};
Then we can even use polymorphism (which was my primary intention BTW):
Derived d1("¯\_(ツ)_/¯");
Derived d2("¯\_(ツ)_/¯");
Set<Base*> set;
set.insert(&d1);
assert(set.contains(&d2));
Hope this helps. Any suggestions are welcome.
template<class T>
class auto_ptr2 {
public:
explicit auto_ptr2(T *p = 0): pointee(p) {}
template<class U>
auto_ptr2(auto_ptr2<U>& rhs): pointee(rhs.release()) {}
~auto_ptr2() { delete pointee; }
template<class U>
auto_ptr2<T>& operator=(auto_ptr2<U>& rhs)
{
if (this != &rhs) reset(rhs.release());
return *this;
}
T& operator*() const { return *pointee; }
T* operator->() const { return pointee; }
T* get() const { return pointee; }
T* release()
{
T *oldPointee = pointee;
pointee = 0;
return oldPointee;
}
void reset(T *p = 0)
{
if (pointee != p) {
delete pointee;
pointee = p;
}
}
private:
T *pointee;
//template<class U> friend class auto_ptr2<U>;
// Question 1> Why we have to define this friend class
// Question 2> I cannot compile this code with above line with VS2010.
// Error 1 error C3772: 'auto_ptr2<T>' : invalid friend template declaration
};
thank you
Why we have to define this friend class
I'm fairly sure you don't; as far as I can see, nothing is referencing the private member of a different template instantiation. You would need it if the copy constructor or assignment operator manipulated rhs.pointee directly, rather than just calling rhs.release().
I cannot compile this code with above line with VS2010.
The declaration should be:
template<class U> friend class auto_ptr2;