BaitMaster
BaitMaster

Reputation: 13

std::string not working with std::set

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

Answers (3)

Grizzly
Grizzly

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

  1. you can call reduce(teststr.c_str(), 7), since c_str() will return an chararray with the contents of the string.
  2. You could rewrite 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).
  3. If you are using c++0x you can use decltype to get from a container to the contained element: 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

kittykitty
kittykitty

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

Travis Gockel
Travis Gockel

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

Related Questions