Checking if a function failed to convert a variable of a given type to std::string

On LearnCPP, I came across code that converts a variable of any given type to an std::string. There's also this warning there: "Note that this solution omits any error checking. It is possible that inserting tX into oStream could fail. An appropriate response would be to throw an exception if the conversion fails.". So I was wondering how to check for that case. Any suggestions, guys?

This is the code shown on the site:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <sstream>
#include <string>
 
template <typename T>
inline std::string ToString(T tX)
{
    std::ostringstream oStream;
    oStream << tX;
    return oStream.str();
}

int main()
{
    using namespace std;
    string sFour(ToString(4));
    string sSixPointSeven(ToString(6.7));
    string sA(ToString('A'));
    cout << sFour << endl;
    cout << sSixPointSeven << endl;
    cout << sA << endl;
}


Note: I haven't tried running this code yet.

Edit: I just tried doing a check, but I don't know how to make it throw the exception to see if it works, so I'd like some help:
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
#include <iostream>
#include <string>
#include <sstream>
#include "keep_window_open.h"

template <typename T>
inline std::string ToString(T tX);

int main()
{
	using namespace std;
	try
	{
		string sFour{ ToString(4) };
		string sSixPointSeven{ ToString(6.7) };
		string sA{ ToString('A') };
		cout << sFour << "\n" << sSixPointSeven << "\n" << sA << "\n";
	}
	catch (exception& e)
	{
		cerr << e.what() << "\n";
		keep_window_open();
	}
	keep_window_open();
	return 0;
}

template <typename T>
inline std::string ToString(T tX)
{
	using namespace std;
	ostringstream oStream;
	if (!((oStream << tX) ? true : false))
	{
		throw exception::exception();
	}
	return oStream.str();
}


Thanks in advance.
Last edited on
1
2
3
4
5
6
7
8
// note: compile-time error if T is not an output-streamable type.
template < typename T > inline std::string ToString( const T& tX )
{
	std::ostringstream stm ;

	if( stm << tX ) return stm.str() ;
        else throw std::runtime_error( "conversion failed" ) ;
}
Last edited on
Thanks. I'll try that.

Edit:
I can't catch the exception correctly for some reason. Please tell me what I'm doing wrong here:
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
template <typename T>
inline std::string ToString(T tX);

class IntArray
{
private:
	int *m_pnData;
	int m_nLength;
public:
	IntArray()
	{
		m_nLength = 0;
		m_pnData = 0;
	}

	IntArray(const int nLength, const int *pnData)
		:m_nLength{ nLength }, m_pnData{ new int[m_nLength] }
	{
	}

        ~IntArray()
	{
		delete[] m_pnData;
		m_nLength = 0;
		m_pnData = 0;
	}

	int getLength() const { return m_nLength; }

	int& operator[](const int nIndex)
	{
		return m_pnData[nIndex];
	}

	friend std::ostream& operator<< (std::ostream &out, const IntArray& anArray)
	{
		out << anArray.m_pnData;
		return out;
	}
};

int main()
{
	using namespace std;
	IntArray anArray;
	for (int nCount = 0; nCount < 10; nCount++)
	{
		anArray[nCount] = nCount;
	}
	try
	{
		string sFour{ ToString(anArray) };
		string sSixPointSeven{ ToString(6.7) };
		string sA{ ToString('A') };
		cout << sFour << "\n" << sSixPointSeven << "\n" << sA << "\n";
	}
	catch (runtime_error &runError(string))
	{
		cerr << runError("conversion failed").what() << "\n";
	}
	catch (...)
	{
		cerr << "Unknown exception caught\n";
	}
	keep_window_open();
	return 0;
}

template <typename T>
inline std::string ToString(T tX)
{
	using namespace std;
	ostringstream oStream;
	if (oStream << tX)
	{
		return oStream.str();
	}
	else
	{
		throw runtime_error("conversion failed");
	}
}
Last edited on
I expect because that exception isn't thrown, because this code segFaults before it gets there when you do this
anArray[nCount] = nCount;
because m_pnData is a null pointer, having been set to the value 0 and never changed.

If I was reviewing this code, I would say that IntArray is what I technically call a "badly designed" class, because it's possible to create it in a state such that when someone tries to use it, it segFaults.
Last edited on
So I need to make sure it's not a null pointer? How do I assign it to a non-Null value, though?
Like this:
m_pnData = new int[m_nLength]
which is being done in the other constructor. Did you write this code? It seems odd that you know how to allocate some memory in a constructor, and at the same time not know how to do it.
Last edited on
I was having trouble actually correctly calling that constructor, I guess.

Anyway, since the IntArray class a vector-like class (sans template, though - template version was shown in another lesson) that I got from LearnCPP itself in an earlier lesson, using the same code from the original class works. But now it's working a bit too well and I don't know what type or variable to use to test the exception.

Here's the code:
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
#include <iostream>
#include <string>
#include <sstream>
#include <stdexcept>
#include "IntArray.h"
#include "ArrayException.h"
#include "keep_window_open.h"

template <typename T>
inline std::string ToString(T tX);

int main()
{
	using namespace std;
	IntArray anArray{ 10 };
	for (int nCount = 0; nCount < anArray.GetLength(); nCount++)
	{
		anArray[nCount] = nCount;
	}
	try
	{
		string sFour{ ToString(anArray[4]) };
		cout << sFour << "\n";
	}
	catch (runtime_error &r)
	{
		cerr << r.what() << "\n";
		keep_window_open();
		return 0;
	}
	catch (exception &e)
	{
		cerr << e.what() << "\n";
		keep_window_open();
		return 1;
	}
	catch (...)
	{
		cerr << "Unknown exception caught\n";
		keep_window_open();
		return 2;
	}
	string sSixPointSeven{ ToString(6.7) };
	string sA{ ToString('A') };
	cout << sSixPointSeven << "\n" << sA << "\n";
	keep_window_open();
	return 0;
}

template <typename T>
inline std::string ToString(T tX)
{
	using namespace std;
	ostringstream oStream;
	if (oStream << tX)
	{
		return oStream.str();
	}
	else
	{
		throw runtime_error("conversion failed");
	}
}


For the class code:
IntArray.h:
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
#ifndef INTARRAY_H
#define INTARRAY_H

class IntArray
{
private:
	int m_nLength;
	int *m_pnData;
public:
	IntArray();
	IntArray(const int nLength);
	~IntArray();
	void Erase();
	int& operator[](const int nIndex);
	int GetLength() const;
	void Reallocate(const int nNewLength);
	void Resize(const int nNewLength);
	void InsertBefore(const int nValue, const int nIndex);
	void Remove(const int nIndex);
	void InsertAtBeginning(const int nValue);
	void InsertAtEnd(const int nValue);
	int GetElement(const int nIndex) const;
	friend std::ostream& operator<< (std::ostream &out, const IntArray &anArray);
};

#endif 


IntArray.cpp:
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
#include <iostream>
#include "IntArray.h"
#include "ArrayException.h"

IntArray::IntArray()
{
	m_nLength = 0;
	m_pnData = 0;
}

IntArray::IntArray(const int nLength)
	:m_pnData{ new int[nLength] }, m_nLength{ nLength }
{
}

IntArray::~IntArray()
{
	delete[] m_pnData;
}

void IntArray::Erase()
{
	delete[] m_pnData;
	m_pnData = 0;
	m_nLength = 0;
}

int& IntArray::operator[](const int nIndex)
{
	if (nIndex < 0 || nIndex >= m_nLength)
	{
		throw ArrayException{ "Invalid index" };
	}
	return m_pnData[nIndex];
}

int IntArray::GetLength() const
{
	return m_nLength;
}

// Reallocate resizes the array.  Any existing elements will be destroyed.
// This function operates quickly.

void IntArray::Reallocate(const int nNewLength)
{
	// First we delete any existing elements
	Erase();

	// If our array is going to be empty now, return here
	if (nNewLength <= 0)
	{
		return;
	}

	// Then we have to allocate new elements
	m_pnData = new int[nNewLength];
	m_nLength = nNewLength;
}

// Resize resizes the array.  Any existing elements will be kept.
// This function operates slowly.
void IntArray::Resize(const int nNewLength)
{
	// If we are resizing to an empty array, do that and return
	if (nNewLength <= 0)
	{
		Erase();
		return;
	}

	// Now we can assume nNewLength is at least 1 element.  This algorithm
	// works as follows: First we are going to allocate a new array.  Then we
	// are going to copy elements from the existing array to the new array.
	// Once that is done, we can destroy the old array, and make m_pnData
	// point to the new array.

	// First we have to allocate a new array
	int *pnData = new int[nNewLength];

	// Then we have to figure out how many elements to copy from the existing
	// array to the new array.  We want to copy as many elements as there are
	// in the smaller of the two arrays.
	if (m_nLength > 0)
	{
		int nElementsToCopy = (nNewLength > m_nLength) ? m_nLength : nNewLength;

		// Now copy the elements one by one
		for (int nIndex = 0; nIndex < nElementsToCopy; nIndex++)
		{
			pnData[nIndex] = m_pnData[nIndex];
		}
	}

	// Now we can delete the old array because we don't need it any more
	delete[] m_pnData;

	// And use the new array instead!  Note that this simply makes m_pnData point
	// to the same address as the new array we dynamically allocated.  Because
	// pnData was dynamically allocated, it won't be destroyed when it goes out of scope.
	m_pnData = pnData;
	m_nLength = nNewLength;
}

void IntArray::InsertBefore(const int nValue, const int nIndex)
{
	// Sanity check our nIndex value
	if (nIndex < 0 || nIndex >= m_nLength)
	{
		throw ArrayException{ "Invalid index" };
	}

	// First create a new array one element larger than the old array
	int *pnData = new int[m_nLength + 1];

	// Copy all of the elements up to the index
	for (int nBefore = 0; nBefore < nIndex; nBefore++)
	{
		pnData[nBefore] = m_pnData[nBefore];
	}

	// Insert our new element into the new array
	pnData[nIndex] = nValue;

	// Copy all of the values after the inserted element
	for (int nAfter = nIndex; nAfter < m_nLength; nAfter++)
	{
		pnData[nAfter + 1] = m_pnData[nAfter];
	}

	// Finally, delete the old array, and use the new array instead
	delete[] m_pnData;
	m_pnData = pnData;
	m_nLength += 1;
}

void IntArray::Remove(const int nIndex)
{
	// Sanity check our nIndex value
	if (nIndex < 0 || nIndex >= m_nLength)
	{
		throw ArrayException{ "Invalid index" };
	}

	// First create a new array one element smaller than the old array
	int *pnData = new int[m_nLength - 1];

	// Copy all of the elements up to the index
	for (int nBefore = 0; nBefore < nIndex; nBefore++)
	{
		pnData[nBefore] = m_pnData[nBefore];
	}

	// Copy all of the values after the removed element
	for (int nAfter = nIndex + 1; nAfter < m_nLength; nAfter++)
	{
		pnData[nAfter - 1] = m_pnData[nAfter];
	}

	// Finally, delete the old array, and use the new array instead
	delete[] m_pnData;
	m_pnData = pnData;
	m_nLength -= 1;
}

void IntArray::InsertAtBeginning(const int nValue)
{
	InsertBefore(nValue, 0);
}

void IntArray::InsertAtEnd(const int nValue)
{
	InsertBefore(nValue, m_nLength);
}

int IntArray::GetElement(const int nIndex) const
{
	return m_pnData[nIndex];
}

std::ostream& operator<< (std::ostream &out, const IntArray &anArray)
{
	out << anArray.m_pnData;
	return out;
}


ArrayException.h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef ARRAYEXCEPTION_H
#define ARRAYEXCEPTION_H

#include <iostream>

class ArrayException
{
private:
	std::string m_strError;
public:
	ArrayException();
	ArrayException(const std::string &strError);
	const std::string& GetError() const;
};

#endif 


ArrayException.cpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include "ArrayException.h"

// not meant to be called
ArrayException::ArrayException()
{

}

ArrayException::ArrayException(const std::string &strError)
	:m_strError{ strError }
{
}

const std::string& ArrayException::GetError() const
{
	return m_strError;
}
Topic archived. No new replies allowed.