How do I implicitly cast between user-defined related templated types?

Hi all! This is my first post to these forums and I'm also fairly new to C++. I was being introduced to the idea of templates and to the idea of classes having static member data and static functions, and I had this great idea to try solve my headaches with always needing to remember to use the delete keyword with memory I allocate dynamically.

I started writing my own very first non-trivial (and non-school-assignment) class. It is what I believe is called a wrapper class, and I am using it to wrap around the horrible, error-prone spawns of satan known as Pointers. I wanted something I can use like a pointer syntatically in most situations yet handle the memory cleanup for me, and I think I've gotten a lot of the functionality I want.

There is one thing that I'm stuck on, however, and before I think about adding more functionality or more "pointer interface faithfullness" to my class, I want to see if I can learn how to get a class to implicity convert between to an object of the same class (but with a different template parameter).

Let me show you the class I have written thus far:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/* A Dam_ptr is a simple smart pointer that should only be assigned or initialized to Dynamically-Allocated Memory
   allocated by the New keyword.  DO NOT ASSIGN A RAW ARRAY TO A Dam_ptr, or the memory management fails.  This class keeps
   track of how many Dam_ptrs are pointing to a specific object, and when there are no more Dam_ptrs pointing to
   that object, that object is automatically cleaned up, without the programmer needing to use the Delete keyword.

   Be careful that any regular pointers still pointing to that memory location will become dangling pointers after
   the last Dam_ptr pointing to the memory location goes out of scope.
          *****THIS BEARS REPEATING: DO NOT ASSIGN A RAW ARRAY (NEW'ED OR OTHERWISE) TO A Dam_ptr***** 
*/

#ifndef DAM_PTR_H
#define DAM_PTR_H

#include <iostream>
using std::cout;

#include <vector>
using std::vector;

// simple struct consisting of a memory address and number of Dam_ptrs pointing to that address.
struct Mem_addr
{
  Mem_addr ( int addr )
        : m_addr ( addr ), m_count ( 1 )
  {}
  
  int m_addr;
  int m_count;
};
// ==============================END OF STRUCT DECLARATION==============================

template< typename T >
class Dam_ptr // *****THIS BEARS REPEATING: DO NOT ASSIGN A RAW ARRAY (NEW'ED OR OTHERWISE) TO A Dam_ptr***** 
{             // *****ONLY ASSIGN DATA/OBJECTS CREATED BY NEW (NOT NEW []) TO Dam_ptrS*****
public:
  Dam_ptr (); // default
  Dam_ptr ( T* ptr ); // initialized with a normal pointer
  Dam_ptr ( const Dam_ptr< T >& copy ); // copy constructor

  ~Dam_ptr (); // destructor

  // overloaded operators
  T& operator* () const; // dereference operator
  T* operator-> () const; // arrow operator

  const Dam_ptr< T >& operator= ( const Dam_ptr< T >& right ); // assignment operator
  const Dam_ptr< T >& operator= ( T* const right ); // overloaded so you can assign a pointer to a Dam_ptr

  template< typename T >
  operator T* () const // cast operator to allow for conversion from a Dam_ptr to a pointer of matching type T
    { return ( m_ptr ); } // implementation here.  Could not write implementation elsewhere for unknown reason

  bool operator== ( const Dam_ptr< T >& right ) const; // equality operator to compare 2 Dam_ptrs
  bool operator== ( const T* right ) const; // equality operator to compare a Dam_ptr to a regular pointer
/*  template< typename T >
  friend bool operator== ( const T* left, const Dam_ptr< T >& right ); // unnecessary since a cast operator is provided?
*/

  bool operator!= ( const Dam_ptr< T >& right ) const; // inequality operator to compare 2 Dam_ptrs
  bool operator!= ( const T* right ) const; // inequality operator to compare a Dam_ptr to a regular pointer

  T* getPtr () const; // returns a copy of the internal pointer
  static int getAddrCount ( int addr ); // returns the number of Dam_ptrs pointing to the object at memory location addr
  static int getAddrCount ( T* ptr ); // returns the number of Dam_ptrs pointing to the object pointed to by ptr.
                                      // also accepts Dam_ptrs due to the presence of the cast operator

private:
  T* m_ptr;
  int m_pos; // index within Addressbook where the memory address number is stored. 
             // -1 means the Dam_ptr points to NULL and hence does not have an entry in AddressBook

  static vector< Mem_addr > AddressBook;
  
  static int getAddrCountAndIndex ( int addr, int& index ); // private utility function
};
#endif
// ==============================END OF CLASS DECLARATION==============================


// defining static date member
template< typename T >
vector< Mem_addr > Dam_ptr< T >::AddressBook;

template< typename T >
Dam_ptr< T >::Dam_ptr () // default constructor.  Sets internal pointer to NULL and m_pos = -1
        : m_ptr ( NULL ), m_pos ( -1 )
{ }

template< typename T >
Dam_ptr< T >::Dam_ptr ( T* ptr ) // constructor taking in a normal pointer as an argument
        : m_ptr ( ptr ), m_pos ( AddressBook.size() )
{
  if ( NULL == ptr ) // if statement is Dam_ptr< T > = 0, then it like the default constructor was called.  Set m_pos = -1
  {
    m_pos = -1;
  }
  else // update AddressBook
  {
    int addr = (int)ptr;
    int count = getAddrCountAndIndex ( addr, m_pos ); // if addr not found, m_pos is set to one beyond the current end of AddressBook 
                                                      // because that's where the new Mem_addr will be placed
    if ( 0 == count )
    {
      AddressBook.push_back ( Mem_addr ( addr ) );
    }
    else
      AddressBook[m_pos].m_count++;
  }
}

template< typename T >
Dam_ptr< T >::Dam_ptr ( const Dam_ptr< T >& copy ) // copy constructor
  : m_ptr ( copy.m_ptr ), m_pos ( copy.m_pos )
{
  if ( NULL != m_ptr ) // update Addressbook
  {
    AddressBook[m_pos].m_count++;
  }
}

template< typename T >
Dam_ptr< T >::~Dam_ptr () // destructor
{
  if ( m_pos > -1 )
  {
    AddressBook[m_pos].m_count--; // decrement the counter of Dam_ptrs pointing to the address
    if ( 0 == AddressBook[m_pos].m_count ) // check if this is the last Dam_ptr pointing to the address
    {
      delete m_ptr;
    }
  } 
}

template< typename T >
T& Dam_ptr< T > ::operator* () const // overloaded dereference operator
{
  return ( *m_ptr );
}

template< typename T >
T* Dam_ptr< T > ::operator-> () const // overloaded arrow operator
{
  return ( m_ptr );
} 


Continue next post because it was too long.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
template< typename T >
const Dam_ptr< T >& Dam_ptr< T >::operator= ( const Dam_ptr< T >& right ) // overloaded assignment operator
{
  if ( this == &right )
    return (*this);

  if ( NULL != m_ptr ) // if m_ptr has a valid entry in AddressBook, decrement the count
  {
    AddressBook[m_pos].m_count--;
    if ( 0 == AddressBook[m_pos].m_count ) // check if this is the last Dam_ptr pointing to the address
    {
      delete m_ptr;
    }
  }

  m_pos = right.m_pos;
  m_ptr = right.m_ptr;

  if ( NULL != m_ptr )
  {
    AddressBook[m_pos].m_count++;
  }

  return (*this);
}

template< typename T >
const Dam_ptr< T >& Dam_ptr< T >::operator= ( T* const right ) // overloaded assignment operator so you can assign a pointer to a Dam_ptr
{
  if ( m_ptr == right )
      return (*this);

  if ( NULL != m_ptr ) // if m_ptr has a valid entry in AddressBook, decrement the count
  {
    AddressBook[m_pos].m_count--;
    if ( 0 == AddressBook[m_pos].m_count ) // check if this is the last Dam_ptr pointing to the address
    {
      delete m_ptr;
    }
  }

  m_ptr = right;

  if ( NULL == right )
  {
    m_pos = -1;
  }
  else // update AddressBook
  {
    int addr = (int)right;
    m_pos = AddressBook.size (); // default the position is at the end, unless the address value is found somewhere in the vector
    int count = getAddrCountAndIndex ( addr, m_pos );

    if ( 0 == count )
    {
      AddressBook.push_back ( Mem_addr ( addr ) );
    }
    else
      AddressBook[m_pos].m_count++;
  }

  return (*this);
}

template< typename T >
bool Dam_ptr< T >::operator== ( const Dam_ptr< T >& right ) const // overloaded equality operator.  Left = Dam_ptr, Right = Dam_ptr
{
  return ( ( this->m_ptr == right.m_ptr ) );
}

template< typename T >
bool Dam_ptr< T >::operator== ( const T* right ) const // overloaded equality operator.  Left = Dam_ptr, Right = pointer to object of type T
{
  return ( ( this->m_ptr == right ) );
} 

template< typename T >
bool Dam_ptr< T >::operator!= ( const Dam_ptr< T >& right ) const // overloaded inequality operator.  Left = Dam_ptr, Right = Dam_ptr
{
  return ( ( this->m_ptr != right.m_ptr ) );
}

template< typename T >
bool Dam_ptr< T >::operator!= ( const T* right ) const // overloaded inequality operator.  Left = Dam_ptr, Right = pointer to object of type T
{
  return ( ( this->m_ptr != right ) );
}

template< typename T >
T* Dam_ptr< T >::getPtr () const // returns a copy of the pointer data member
{ return m_ptr; }

template< typename T >
int Dam_ptr< T >::getAddrCount ( int addr ) // static public member function to return how many Dam_ptrs are pointing to memory location addr
{
  for ( int i = 0; i < AddressBook.size(); i++ )
  {
    if ( addr == AddressBook[i].m_addr )
      return ( AddressBook[i].m_count );
  }
  return 0;
}

template< typename T >
int Dam_ptr< T >::getAddrCount ( T* ptr ) // static public member function to return how many Dam_ptrs are pointing to the object pointed to by ptr
{
  return ( Dam_ptr< T >::getAddrCount ( (int)ptr ) );
}

template< typename T >
int Dam_ptr< T >::getAddrCountAndIndex ( int addr, int& index ) // static private member function to return how many Dam_ptrs are pointing to memory location addr, and also the index within AddressBook
{                                                               // index is left unchanged if addr is not found within AddressBook
  for ( int i = 0; i < AddressBook.size(); i++ )
  {
    if ( addr == AddressBook[i].m_addr )
    {
      index = i;
      return ( AddressBook[i].m_count );
    }
  }
  return 0;
}


So my code compiles just fine on Microsoft Visual Studio 2010 (but not on g++. I think the Microsoft compiler is smarter about type conversions). I can do things like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// This block releases memory just fine
{
  Dam_ptr < int > dptr = new int ( 10 );
  int* ptr_to_int = new int ( 20 );
  dptr = ptr_to_int;
}

// These conversions work fine
void f ( int* p )
{ /* implement f */ }

void g ( Dam_ptr< int > dp )
{ /* implement g */ }

f ( dptr ); // this call works because Dam_ptr<T> can conert implicitly to T*
g ( ptr_to_int ); // and vice versa 


Here's what I can't get to work. For example, if I have a function f () that takes a Dam_ptr < double > as a parameter, I feel that I should be able to pass to f () a Dam_ptr < int > or even a int*, since if there is a finction g () that takes a double as an argument, then I can pass an int to the function and it'd work just fine.

So, how can I, without explicitly writing out every single possible conversion (theoretically infinite), let my Dam_ptr < SomeType> convert to Dam_ptr< RelatedType >, assuming that an implicit conversion can be done with the underlying types (like say...a Base Class and a Derived Class or a double and an int).

That is my main question. Secondary questions include: What other functionalities should this class logically provide? Are there any glaring design errors (aka is there anything I can do obviously better) in this class?

If this code is horribly mangled and makes you want to facepalm, then I apologize up front. I am no master of pointers and I am just beginning to learn templates and the issues they bring, so please bear with me. Thank you very much!
A quick advice: don't write so intimidating posts. Don't post all your code at once. Just point the part you need help about.

Being clear about this I haven't read your code.

Implicit conversion can be done with various ways:
if you have declared a constructor with argument, through an operator etc. In which context you want the conversion to appear? This is how you should examine how to implement implicit conversion.

To be more clear if you have defined a class that contains a string and an int and you want to implicitly convert objects of this class when working with ints and * define an overloaded version of operator* for your class.

I am not an expert either (as you may have guessed) so take my advice mildly.

One last think I remembered: implicit conversions offer flexibility but also ambiguity and you should be careful about this also.
My class holds a pointer to typename T, and I've overloaded operators for my class (including * ) to make it look and work like a pointer. What I'm unable to do, hoever, it do something like have an array (or vector) of Dam_ptr <Base Class> and have some of those Dam_ptr be Dam_ptr <Derived Class> instead. It works with RAW pointers but I guess there's no relationship of inheritence once contained within my wrapper class.
Topic archived. No new replies allowed.