1-How to specialize a class template?

Hello Community,

First of all sorry for opening up a new topic on essentially the problem. [See: http://www.cplusplus.com/forum/beginner/231931/ ] Since this is about a different set of problems/questions in relation to the original topic, I hope this is ok ...

Currently the first class specialization is in the works. As opposed to the main template class I resorted to raw pointers, and what Cubbi in his last reply mentioned about initializer list vs 'old-style' constructor syntax. I tried to work with unique_ptr in this case, but failed getting results.

What I would like to request help with:
Is the way the specialized template is written correct?
- How can I use unique_ptr in this scenario?
- Here is an example code (scroll down to the last reply in there and v to open) https://teratail.com/questions/55841 This is esentially what I tried to translate into my code which did not work

Some general help I would need with:
My original template contains a push_back() and pop_back() function, which should of course be part of the specialized template. The question here is what would make sense?
-> strcpy() every time a new item is added? Or something else?

What should I do with/in the pop_back?
Here some pseudo-code, better yet, some ideas what to do with that, would be very helpful.

[Add:] There is also the overloaded [] which I am not too sure how to write, here a little help would be appreciated. (Or generally what to do with it)

Any general input as to what can be done better/differently is also highly appreciated.

[Since this post would be too long with code this is added in the reply]
Last edited on
Finally here is the class/specialized class templates plus driver:

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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <memory>

class ExceptionClass 
{
	private:
		int errIndex;

	public:
		ExceptionClass(int subPos) : errIndex(subPos)
		{ }

		int getIndex() const
		{ return errIndex; }
};

template <class T>
class SimpleVector
{
	private:
		std::unique_ptr<T []> aptr;
		int capacity;
		int arraySize;

	public:
		SimpleVector();
		SimpleVector(int);
		SimpleVector(const SimpleVector &);
		~SimpleVector()
		{ }

		void push_back(const T &);
		void pop_back();

		int getArraySize() const
		{ return arraySize; }

		int getArrayCapacity() const
		{ return capacity; }

		void print() const;
		
		// Overloaded operator functions
		T &operator [](int);
		T &operator =(const SimpleVector &);
};

/* **********************************************************
			SimpleVector<T>::SimpleVector() - Constructor
   ********************************************************** */

template <class T>
SimpleVector<T>::SimpleVector() : aptr(std::make_unique<T []>(0)), capacity(2), arraySize(0)
{ }

/* **********************************************************
			SimpleVector<T>::SimpleVector() : int
		 - Constructor
	Sets the size of the array and allocates memory.
   ********************************************************** */

template <class T>
SimpleVector<T>::SimpleVector(int arrSize) : 
	aptr(std::make_unique<T []>(arrSize)), capacity(arrSize), arraySize(arrSize)
{ }

/* **********************************************************
			SimpleVector<T>::SimpleVector() : const obj &
		 - Copy Constructor
   ********************************************************** */

template <class T>
SimpleVector<T>::SimpleVector(const SimpleVector &obj) :
	aptr(std::make_unique<T []>(obj.arraySize)), arraySize(obj.arraySize)
{
	std::copy(obj.aptr.get(), obj.aptr.get() + arraySize, aptr.get());
}

/* **********************************************************
			T &SimpleVector<T>::operator=() : const obj &
		 - Overloaded assignment operator function
	********************************************************** */

template <class T>
T &SimpleVector<T>::operator=(const SimpleVector &right)
{
	if (this != &right)
	{
		if (arraySize != right.arraySize)
		{
			capacity = right.capacity;
			arraySize = right.arraySize;
		
			aptr = std::make_unique<T []>(right.capacity);
		}
			
		for (int count = 0; count < arraySize; count++)
		{
			aptr[count] = right.aptr[count];
		}
	}

	return *this;
}

/* **********************************************************
			SimpleVector<T>::push_back() : T
	This function emulates the vector's push_back() function.
	********************************************************** */

template <class T>
void SimpleVector<T>::push_back(const T &tempVal)
{
	        if (arraySize >= capacity)
	{
		auto newCapacity = capacity * 2 + 1;
		auto tmpPtr = std::make_unique<T []>(newCapacity);

		std::copy_n(aptr.get(), arraySize, tmpPtr.get());
		aptr = std::move(tmpPtr);
		capacity = newCapacity;
	}
	
	aptr.get()[arraySize++] = tempVal;	
}

/* **********************************************************
		SimpleVector<T>::pop_back()
    This function emulates the vector's pop_back() function.
    ********************************************************** */

template <class T>
void SimpleVector<T>::pop_back()
{
	if (arraySize != 0)
	{	
		aptr.get()[--arraySize] = T();
	}
	else
	{
		throw ExceptionClass(arraySize);
	}
}

/* **********************************************************
			T &SimpleVector<T>::operator[] : int
		 - Overloaded [] operator function
	Returns a reference to the element in the array indexed by
	the subscript.
   ********************************************************** */

template <class T>
T &SimpleVector<T>::operator[](int subPos)
{
	if (subPos < 0 || subPos >= arraySize)
	{
		throw ExceptionClass(subPos);
	}

	return aptr[subPos];
}

/* **********************************************************
			SimpleVector<T>::print()
	Outputs the array's contents to screen.
   ********************************************************** */

template <class T>
void SimpleVector<T>::print() const
{
	for (int count = 0; count < arraySize; count++)
	{
		count > 0 && (count % 10 == 0) ? 
			std::cout << aptr[count] << std::endl : 
			std::cout << aptr[count] << " ";
	}
	std::cout << std::endl << std::endl;
}

#include <string>

int main()
{
		const size_t SIZE = 25;
	//int subPosition = 0;
	int addValue = 0;
	//int result = 0;

	SimpleVector<int> initTest;
	std::cout << "Size: " << initTest.getArraySize();
	std::cout << "Capacity: " << initTest.getArrayCapacity() << std::endl;

	SimpleVector<int> intVect(SIZE);
	SimpleVector<double> dVect(SIZE);

	for (size_t i = 0; i < 5; i++)
	{
		intVect[i] = (i + 2);
		dVect[i] = (i * 2.4);
	}

	intVect.print();
	dVect.print();

	std::cout << "Size: " << intVect.getArraySize();
       std::cout << "Capacity: " << intVect.getArrayCapacity() << std::endl;

	std::cout << "Enter a value: ";
	std::cin >> addValue;

	try
	{
		intVect.push_back(addValue);
		intVect.print();

	        std::cout << "Size: " << intVect.getArraySize();
	        std::cout << "Capacity: " << intVect.getArrayCapacity() << std::endl;
	}
	catch (ExceptionClass r)
	{
		std::cout << r.getIndex() << " out of range!" << std::endl;
	}

	std::cout << "\nTh ... th ... that's all, Doc!";

	return 0;
}
Last edited on
My original template contains a push_back() and pop_back() function, which should of course be part of the specialized template. The question here is what would make sense?
-> strcpy() every time a new item is added? Or something else?

Yes. I'd use std::copy or memcpy as an improvement, since the length of the string is already known and there's no need to waste time with strcpy (applies to the constructor too).

errorHandler is an odd name for the function that allocates a buffer, and although "log and terminate" is a valid strategy for handling out-of-memory (Google does it), it makes your code unusable in reliable software: imagine a Photoshop or an MS Word or even Notepad++ using your SimpleVector to load a large document from disk: you're going to crash it and lose all unsaved work. A web server using your SimpleVector would be easy to crash and DoS the site. Let the exceptions go. If you must log-and-terminate, place try and catch in the main function.

There is also the overloaded [] which I am not too sure how to write, here a little help would be appreciated. (Or generally what to do with it)

see http://en.cppreference.com/w/cpp/language/operators#Array_subscript_operator (and the rest of that page for the other operators)
Last edited on
Hello Cubbi, first of all thank you very much for having taken time to have a look at it!

The errorHandler function, well, this was again one of those stupid things, done for the sake of getting something that works. I have a class for it, so this will be used instead ...

[Misenna] My original template contains a push_back() and pop_back() function, which should of course be part of the specialized template. The question here is what would make sense?
-> strcpy() every time a new item is added? Or something else?

[Cubbi] Yes. I'd use std::copy or memcpy as an improvement, since the length of the string is already known and there's no need to waste time with strcpy (applies to the constructor too).


I seem to have lost you there. What do you mean? Can you give me an example please?

--

What I really wish to be able to do is to further use the unique_ptr approach you have shown me. This would be a major improvement. Specifically when it comes to the memory management part of the story.

To make it happen, and it is difficult as hell to even ask, I would need code similar to the one you have given in the original discussion, which I then worked out my version of the template classes:

[Cubbi]speaking of which.. here's how I'd write the core part of that SimpleVector in today's C++


So, here goes nothing: Would it be possible to provide and example as how you would write the core part of the specialized template class with unique_ptr as well as well?
1
2
3
4
5
6
7
/*
SimpleVector<T>::push_back()
This function emulates the vector's push_back() function.

SimpleVector<T>::pop_back()
This function emulates the vector's pop_back() function.
*/

Would you explain line 130?

The std::vector has size and capacity.
Internally, the vector has an array that can hold capacity elements.
Externally, the vector holds size elements.
Every push and pop do change the size, but the capacity changes only when necessary.

Your SimpleVector creates a new array every time you do push. Every time.
However, pop does not change the internal array.

Lets test:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
SimpleVector<Foo> bar( 7 );
// arraySize==7; size of array == 7; 7 default Foos in bar

bar.push_back( Foo( 42 ) );
// arraySize==8; size of array == 8; 7 default and one special Foos in bar
the
bar.pop_back();
// arraySize==7; size of array == 8; 7 default Foo in bar +1 special "out of range"

bar.pop_back();
// arraySize==6; size of array == 8; 6 default Foo in bar,  +1 default +1 special "out of range"

bar.push_back( Foo( 45 ) );
// arraySize==7; size of array == 7; 6 default and one special Foos in bar 

In other words, does to original template look sound?


What does your specialization represent? A C-string. One C-string.
All the other types from template will have N objects.
The SimpleVector<char*> has N+1 objects; each object is a char and the last of them is '\0'

The generic template adds one element to the end of the array with the push_back().
So does the specialization, but it has to maintain the null at end too.

For pop_back() decrementing length is not enough in C-string; one has to write a null too.

The operator[] logically returns one element (by value or a reference). Easy, when element is a char.
Can one get the trailing null too? Can one write to the trailing null?
What if one writes a null to the middle of a string via reference?


There are two sides:
1. What an object represents and how it should be used?
2. What does it take to keep the object internally consistent, when it is used?


The nice part of unique_ptr is that it manages memory for you; no need for explicit deallocation.
The tricky part are copy operations (constructor, assignment) because the "default" is a move.
keskiverto
Would you explain line 130?


A cheap attempt to 'reduce' the number of elements.

keskiverto
The std::vector has size and capacity.
Internally, the vector has an array that can hold capacity elements.
Externally, the vector holds size elements.
Every push and pop do change the size, but the capacity changes only when necessary.

Your SimpleVector creates a new array every time you do push. Every time.
However, pop does not change the internal array.

Thank you for pointing out where my attemp fails. I considered only the size, but never thought about capacity. Don't ask why this is, but somehow size/capacity were somehow the same thing in my mind. At least this is something that seems rather easy to correct.

-

I will think about your other questions, while working on the new revision, which may or may not turn out to be correct and sound.
So, I have now changed the push_back() function. Is this about how it should be?

For the purpose of testing, please run the one in the second post. I will update the code there instead of constantly posting it all over and over again when something has changed, and instead post questions and relevant code in the new reply.

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
/* **********************************************************
			SimpleVector<T>::push_back() : T
	This function emulates the vector's push_back() function.
	********************************************************** */

template <class T>
void SimpleVector<T>::push_back(const T &tempVal)
{
	// Maybe this should go into its own function? > meaning
	// all that is in the if clause?
	int newCapacity = capacity * 2 + 1;

	if (arraySize == capacity)
	{
		std::unique_ptr<T[]> tmpPtr = std::make_unique<T []>(newCapacity);

		for (int count = 0; count < arraySize; count++)
		{
			tmpPtr.get()[count] = aptr.get()[count];
		}

		tmpPtr.get()[arraySize] = tempVal;
		aptr = std::move(tmpPtr);

		capacity = newCapacity;
	}
	
	aptr.get()[arraySize++] = tempVal;	
}
Last edited on
1. Why do you calculate newCapacity outside of the if clause? It is used only inside.

2. Why line 22? You do it on line 28.

3. http://www.cplusplus.com/reference/memory/unique_ptr/operator[]/
but how about:
http://www.cplusplus.com/reference/algorithm/copy_n/
1
2
3
auto newCapacity = capacity * 2 + 1;
auto tmpPtr = std::make_unique<T []>( newCapacity );
std::copy_n( aptr.get(), arraySize, tmpPtr.get() );



There is a slight difference to std::vector. If I do:
1
2
std::vector<Foo> bar; // empty
bar.reserve( 42 ); // increase capacity 

Q: How many initialized Foo objects do I have in the bar?

There is no practical difference to SimpleVector with basic types, but if Foo('s default constructor) is mind-bogglingly complex and heavy ...
keskiverto1. Why do you calculate newCapacity outside of the if clause? It is used only inside.


There was a crash so I put it outside the clause; The error however resulted from the attempt of telling the constructor: Hey, make capacity(arraySize + 1), thank you ... I forgot, so the calculation remained outside the if clause.

keskiverto
2. Why line 22? You do it on line 28.

With another if clause this would make the purpose of having both there clearer. The one in the if clause only executes and does its thing when the capacity increases; So, in the current state of things it says: "If arraySize >= capacity" do this, and do the other thing anyway.

keskiverto
3. http://www.cplusplus.com/reference/memory/unique_ptr/operator[]/
but how about:
http://www.cplusplus.com/reference/algorithm/copy_n/

1
2
3
auto newCapacity = capacity * 2 + 1;
auto tmpPtr = std::make_unique<T []>( newCapacity );
std::copy_n( aptr.get(), arraySize, tmpPtr.get() );



This would be nice indeed! I tried to achieve this with std::copy() just like it in the copy ctor. (which will also have to change, there is nothing be done with the capacity as it is.)

But one thing after the other. Is this correct? (The code in post 2 has been updated to reflect the change).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* **********************************************************
			SimpleVector<T>::push_back() : T
	This function emulates the vector's push_back() function.
	********************************************************** */

template <class T>
void SimpleVector<T>::push_back(const T &tempVal)
{
        if (arraySize >= capacity)
	{
		auto newCapacity = capacity * 2 + 1;
		auto tmpPtr = std::make_unique<T []>(newCapacity);

                std::copy(aptr.get(), aptr.get() + arraySize, tmpPtr.get());

		// std::copy_n(aptr.get(), arraySize, tmpPtr.get());
		aptr = std::move(tmpPtr);
		capacity = newCapacity;
	}
	
	aptr.get()[arraySize++] = tempVal;
}


Note: While VS does not complain about using std::copy_n() the wandbox and cppshell websites both tell that:

std::copy_n is not part of std namespace ... So I use the copy version, which does work there. So, this is commented out here;

Edit:
There is a slight difference to std::vector. If I do:
1
2
std::vector<Foo> bar; // empty
bar.reserve( 42 ); // increase capacity  


Q: How many initialized Foo objects do I have in the bar?

There is no practical difference to SimpleVector with basic types, but if Foo('s default constructor) is mind-bogglingly complex and heavy ...

So, what you are suggesting is to add a function that reserves some memory just in case? Please explain. Also, have I thanked you for your continued support? I think I did not ... *shame on me* So, here is a BIG THANK YOU for it, it is really appreciated!
Last edited on
Q: How many initialized Foo objects do I have in the bar?

A: 0

The std::vector allocates raw memory and creates objects only when necessary.
"Placement new" differs from new and new[].

I don't suggest making SimpleVector into full std::vector. I just point out that there is much under the hood.
Misenna wrote:
While VS does not complain about using std::copy_n() the wandbox and cppshell websites both tell that: std::copy_n is not part of std namespace ...

did you forget to #include <algorithm>? https://wandbox.org/permlink/AT1IEEAcSDK2rgpw

Misenna wrote:
I considered only the size, but never thought about capacity.

it seemed like it was intentional. Once you add capacity, your SimpleVector is not going to be so simple anymore (as keskiverto above points out too). In fact, classes that manage a dynamic array with just size are fairly common in real code, although such classes don't usually offer push_back/pop_back/resize.
Last edited on
keskiverto
I don't suggest making SimpleVector into full std::vector. I just point out that there is much under the hood.

Got it! The intention isn't to make it into a full vector class now as opposed to before. However, at least that which is there should as closely emulate what a real vector would do. Which in this case is the push_back and pop_back functionality. As you pointed out, my original approach was flawed.

Cubbi
Misenna wrote:
While VS does not complain about using std::copy_n() the wandbox and cppshell websites both tell that: std::copy_n is not part of std namespace ...

did you forget to #include <algorithm>? https://wandbox.org/permlink/AT1IEEAcSDK2rgpw

No, forgot to copy it! No wonder wandbox was complaining ... *stupid me*

Cubbi:

Misenna wrote:
I considered only the size, but never thought about capacity.

it seemed like it was intentional. Once you add capacity, your SimpleVector is not going to be so simple anymore (as keskiverto above points out too). In fact, classes that manage a dynamic array with just size are fairly common in real code, although such classes don't usually offer push_back/pop_back/resize.


It was intentional. Fair to say that this decision was based on the assumption that it would work with array under the hood, so this seemed to do. Since it should emulate a vector's functions, it should then at least sound, and modeled close to what a real vector would do. As keskiverto pointed out: "In other words, does to original template look sound?" It was not, now, hopefully, it is.

-

Here now is the reworked pop_back() function (here is the wandbox link: https://wandbox.org/permlink/RspiBupj3uUS3ta8 the code in the second post has also been updated):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* **********************************************************
			SimpleVector<T>::pop_back()
	This function emulates the vector's pop_back() function.
	********************************************************** */

template <class T>
void SimpleVector<T>::pop_back()
{
	if (arraySize != 0)
	{
		arraySize--;
	}
	
	if (arraySize <= capacity / 2)
	{
		auto newCapacity = capacity / 2;
		auto tmpPtr = std::make_unique<T []>(newCapacity);

		std::copy_n(aptr.get(), arraySize, tmpPtr.get());
		aptr = std::move(tmpPtr);
		capacity = newCapacity;
	}
}


Is this now as it should be?
On the whole, is there still something not right? (maybe with the constructor - the standard and the one taking size as argument?)
Last edited on
Is this now as it should be?

real std::vector doesn't shrink capacity in pop_back, and, more significantly, always calls the destructor of the last T

Though making an auto-shrinking vector is perhaps a justifiable reason for writing your own.
Last edited on
Cubbi
real std::vector doesn't shrink capacity in pop_back, and, more significantly, always calls the destructor of the last T

Though making an auto-shrinking vector is perhaps a justifiable reason for writing your own.

Some way down the road, when there is more understanding how vectors work internally, it will be worth the attempt. For now, this will/should do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* **********************************************************
			SimpleVector<T>::pop_back()
	This function emulates the vector's pop_back() function.
	********************************************************** */

template <class T>
void SimpleVector<T>::pop_back()
{
	if (arraySize != 0)
	{
		aptr.get()[--arraySize] = T();
	}
	else
	{
		throw ExceptionClass(arraySize);
	}
}


https://wandbox.org/permlink/LpXl2Tw07oeJ8fsR

If there is no critique or any problems that I may not be able to spot?

If no, I will try to change the specialized version of this class once again. (or as in the refrain: Over and over, again and again, ... https://www.youtube.com/watch?v=e7eyohkTviI ;-))

For this, though, I would still need a little nutch in the right direction. So, since it helps to take things step by step, I will start with the class itself. In the book it is shown as: "template <> class SimpleVector<char *>"

But how about this - if going with unique_ptr?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// v1
template <>
class SimpleVector<std::unique_ptr<char []>>
{
   // ... 
}

// v2
template <>
class SimpleVector<char *[]>
{
    private:
       std::unique_ptr<char *[]> sptr;
}

// v3
template <>
class SimpleVector<std::unique_ptr<char *[]>>
{
    private:
       std::unique_ptr<char *[]> sptr;
}


Which, if any of these is correct, would be the correct one to go with?
Last edited on
The unique_ptr is implementation detail and that should not be the concern of the users.

Likewise, we (users) don't really care what std::vector does when we call push_back() or pop_back(). It is the capacity(), reserve(), and shrink_to_fit() that hint that there is more than meets the eye, but usually we don't (have to) care.


If the book says template <> class SimpleVector<char *>, then go with it.
Does the book say whether this specialization should represent one C-string or an array of C-strings?


Ultimately, the question is what the book/you want to learn with this exercise.


Here is a different SimpleVector. Can you tell why one would write such a thing?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <class T>
class SimpleVector
{
private:
	std::vector<T> aptr;

public:
	SimpleVector();
	SimpleVector( int );
	SimpleVector( const SimpleVector & );
	~SimpleVector();
	void push_back( const T & );
	void pop_back();
	int getArraySize() const;
	void print() const;
	T& operator [] ( int );
	T& operator = ( const SimpleVector & );
};
Last edited on
keskiverto
If the book says template <> class SimpleVector<char *>, then go with it.

Does the book say whether this specialization should represent one C-string or an array of C-strings?


What the book says is:

Main Section 16.4: "For example, the declaration of a specialized version of the SimpleVector class might start like this:

class SimpleVector<char *>

The compiler would know that this version of the SimpleVector class is intended for the char * data type. Anytime an object is defined of the type SimpleVector<char *>, the compiler will use this template to generate the code.


The problem statement says:


"In this chapter, the section Specialized Templates within Section 16.4 describes
how to design templates that are specialized for one particular data type. The
section introduces a method for specializing a version of the SimpleVector class
template so it will work with strings. Complete the specialization for both the
SimpleVector and SearchableVector templates. Demonstrate them with a simple
driver program."


There is no mention whether it should work with single characters or not.


keskiverto
Ultimately, the question is what the book/you want to learn with this exercise.

As far as the book is concerned, it seems to be: Figure out how to write specialized classes. Normally it is about putting into practice that which has already been learned by applying this knowledge to solve a given problem. In this case, the only clue is: class SimpleVector<char *>, you know how to write a 'normal' template, so - do the same for the specialized version.

As far as I am concerned, I have no expectation what there is to learn from the experience of trying to solve it. If there is something I wish to learn is how it is done correctly, so I can finally close the chapter. This is the only unsolved problem out of 13.

-

As to your question, if you mean writing in such way, having:

1
2
private:
	std::vector<T> aptr;


I would not know why. Maybe you can be more specific as to what it is you are asking and I will try to answer.
Why would someone write a SimpleVector that is implemented with std::vector?
Why bother, since you can use std::vector directly?
Writing wrappers for members is not very educative.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Bar
{
  // code
};

template <typename T>
class Foo
{
  // code
};

template <>
class Foo<char *>
{
  // code
};

Writing Bar is what writing a class is.

Writing a template is similar, except the additional template syntax. Also, compiler needs help with dependent names. http://en.cppreference.com/w/cpp/keyword/typename

The full specialization is again very much like writing a concrete Bar. Just a tiny bit of extra <> in syntax. There will be duplication, because you want to keep the specialization similar in spirit to the un-specialized template.

a version of the SimpleVector so it will work with strings.

I think that it does not matter how you interpret the "works with strings" as long as it performs the actions that you want it to do.
keskiverto, I see the point. This is not only true for the vector vs. array (not the array class) vs. array, or raw vs. smart-pointers, you name it. This is part of the learning process.

-

By going over all of your resources once more, studying what you and Cubbi have given me, thinking about what both of you suggested, and doing some further study, things finally start to make sense. Particularly what you pointed out here:

keskiverto:
The full specialization is again very much like writing a concrete Bar. Just a tiny bit of extra <> in syntax. There will be duplication, because you want to keep the specialization similar in spirit to the un-specialized template.


This is part of the root cause of the problem as relates to the specialization part causing me so much trouble. I wanted too much. I did not want to have all of the things that make up a class being written inline, all definitions should be there, and the implementation 'outside.' This added an unexpected layer of extra difficulty. For instance, trying to write an implementation of the overloaded = operator:

1
2
3
4
5
// header section
SimpleVector &operator =(const SimpleVector &);

// implementation
SimpleVector<char *> &SimpleVector<char *>::operator =(const SimpleVector &obj)


In other words more complicated syntax, that was causing confusion on the one hand, on the other to awkward and likely wrong syntax, and in consequence leading to my getting lost on the way.

-

To not make this into a long post, here is what I have now, which I hope to get some input on. First here is the link https://wandbox.org/permlink/mH9Rjq4zXnmwORht

Second, here is the start of the specialized class:

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
template <>
class SimpleVector<char []>
{
	private:
		std::unique_ptr<char []> sptr;
		size_t capacity;
		size_t length;

	public:
		// Constructor
		SimpleVector() : capacity(1), length(0)
		{ 
			sptr = std::make_unique<char[]>(capacity);
			sptr.get()[capacity] = '\0';

			std::cout << sptr[0] << " Cap: " << capacity;
		}

		// Constructor
		SimpleVector(size_t len) : capacity(len + 1), length(len)
		{
			sptr = std::make_unique<char[]>(capacity);
			sptr.get()[0] = '\0';
		}

		SimpleVector(const char *tempPtr) : 
			capacity(strlen(tempPtr)+1), length(strlen(tempPtr))
		{
			sptr = std::make_unique<char[]>(capacity);
			std::memcpy(sptr.get(), tempPtr, capacity);
			//sptr.get()[0] = '\0';
		}

		// Destructor
		~SimpleVector()
		{
		}

		// Overloaded Operator =
		SimpleVector &operator =(const SimpleVector &left)
		{
			if (this == &left)
			{
				std::cout << "\nWarning: Self assignment ..." << std::endl;
				return *this;
			}
			else
			{
				length = left.length;
				capacity = left.capacity;
				sptr = std::make_unique<char[]>(left.capacity);
				std::memcpy(sptr.get(), left.sptr.get(), capacity);
			}

			return *this;
		}

		// Overloaded Operator [] (mutable version)
		char &operator [](size_t subPos)
		{
			return sptr.get()[subPos];
		}

		// Does not work ... const char is different or is it?! :-o
		// Overloaded Operator [] (non mutable version)
		//const char &operator[](size_t subPos)
		//{
		//	return sptr.get()[subPos];
		//}

		void print()
		{ 
			std::cout << '*';
			for (int i = 0; i < length; i++)
			{
				std::cout << sptr[i];
			}
			std::cout << '*' << std::endl;
		}
};
Topic archived. No new replies allowed.