Reputation: 13
I'm doing a programming question from C++ Primer Plus which asks me to make a template
function that returns the number of unique elements in an array. I don't understand why
line 13 causes an error while compiling as to my knowledge, a std::string behaves like an array.
This is my code:
#include <iostream>
#include <set>
template <typename T>
int reduce(T ar[], int n);
int main()
{
long test[] = {1, 2, 1, 3, 3, 4, 1};
std::string testStr = "testing";
std::cout << reduce(test, 6) << std::endl;
std::cout << reduce(testStr, 7) << std::endl;
std::cin.get();
return 0;
}
template <typename T>
int reduce(T ar[], int n)
{
std::set<T> test;
for(int i = 0; i < n; i++)
{
test.insert(ar[i]);
}
return test.size();
}
Upvotes: 1
Views: 1993
Reputation: 20191
The answer is quite simple: std::string
is not an array.
It behaves like an array so far as you can access the elements using the [] operator, but it is simply not the same data type as char[]
. As a matter of fact the standard doesn't even guarantee that it's stored like an array (meaning continously). T[]
will only match to array of, not objects which can be used arraylike.
In order to solve this you have several options
reduce(teststr.c_str(), 7)
, since c_str()
will return an chararray with the contents of the string.reduce
as template <typename T, typename U> int reduce(U ar, int n)
and call it as reduce<long>(test, 6)
and reduce<char>(testStr, 7)
. The second template parameter is necessary, since there is no unified way to get from the container to the element (except in c++0x/using compiler extensions).template <typename T>int reduce(T ar, int n)
and std::set<decltype(ar[0])> test;
(rest of the code remains unchanged, and somehow I seem to have trouble with code block sections so just these two lines here.Of course in c++ one would typically write such a function in terms of iterators (see Travis Gockels answer), since that's simply a more flexible and better supported way.
Upvotes: 2
Reputation: 221
You may be confusing std::strings with built-in character arrays. std::strings are not arrays, though they behave similarly to arrays (the class has an overloaded [] operator) and contain arrays (which you can access through c_str()).
If you replace line 10 with
char testStr[] = "testing";
Your program will compile and run.
Or, you could try something like:
#include <iostream>
#include <set>
template <typename T>
int reduce(const T* ar, int n);
int main()
{
long test[] = {1, 2, 1, 3, 3, 4, 1};
std::string testStr = "testing";
std::cout << reduce(test, 7) << std::endl;
std::cout << reduce(testStr.c_str(), testStr.size()) << std::endl;
std::cin.get();
return 0;
}
template <typename T>
int reduce (const T* ar, int n)
{
std::set<T> test;
for(int i = 0; i < n; i++)
{
test.insert(ar[i]);
}
return test.size();
}
Upvotes: 1
Reputation: 27633
Following up my immediate response that std::string
is not an array, this is the way a C++ person might accomplish the task you're looking for.
#include <iterator>
#include <iostream>
#include <set>
// instead of taking an array and length, just take where you want to start and where
// you want to stop.
template <typename TForwardIterator>
int reduce(TForwardIterator iter, TForwardIterator end)
{
// This is hideous syntax to get the type of object the iterator is describing.
// For std::string, it is char...for T*, it is T.
// I apologize for C++, I'm not sure there is a better way to do this.
typedef typename std::iterator_traits<TForwardIterator>::value_type value_type;
std::set<value_type> set;
// instead of forcing the objects to be an array type, use iterators!
for (; iter != end; ++iter)
set.insert(*iter);
return set.size();
}
int main()
{
long test[] = {1, 2, 1, 3, 3, 4, 1};
std::string testStr = "testing";
// begin() and end() are iterators you'll find on all the container types
std::cout << reduce(testStr.begin(), testStr.end()) << std::endl;
// pointers are iterators, too!
std::cout << reduce(test, test + 7) << std::endl;
return 0;
}
Upvotes: 4