He all

I am currently working a on a problem to convert the entire contents of an STL container into a string. For example if I had this:

int foo[] = {1, 2, 3, 4, 5};
vector<int> bar(foo, foo + 5);

I would like to convert bar into the string "1 2 3 4 5". I decided to use templates with this so I could convert most of the STL containers. When writing my function for converting to a string I also created a class that takes a single element from the container and converts it to a string. The code that I have works the way I want it to right now. My question is I think I might have made this more complex than it needs to be but I'm trying to make it as flexible as I can. That's where the conversion class comes in. I think that it should be there so any type of data can be passed into the function and it can be converted. In my code I just wrote one for numbers.

// StringAndNumberConverter.h

#ifndef STRINGANDNUMBERCONVERTER_H
#define STRINGANDNUMBERCONVERTER_H

#include <sstream>
#include <string>

template <typename T>
std::string NumberToString(T number)
{
    std::string output;
    std::stringstream ss;
    ss << number;
    ss >> output;
    return output;
}

#endif
// ContainerToString.h

#ifndef CONTAINERTOSTRING_H
#define CONTAINERTOSTRING_H

#include <string>
#include <functional>
#include "StringAndNumberConverter.h"

// converts single elements into strings
// can be used for all forms of numbers.
template<typename T>
class NumberConverter : std::unary_function<T, std::string>
{
public:
    std::string operator()(T object)
    {
        return NumberToString(object);  // simple stringstream conversion technique
    }
};


// goes through the container and calls the ConvertFunction on each element
// then adds it to the builder string
template <typename RandomAcessIterator, typename ConvertFunction>
std::string ContainerToString(RandomAcessIterator first, RandomAcessIterator last, ConvertFunction function)
{
    std::string builder;
    while (first != last)
    {
        builder += function(*first) + " ";
        first++;
    }
    builder.erase(builder.size() - 1, 1);
    return builder;
}

#endif
// Main.cpp

#include <iostream>
#include <string>
#include <vector>
#include <set>
#include "ContainerToString.h"

using namespace std;

int main()
{
    double foo[] = {10.1, 20.2, 30.3, 40.4, 50.5};
    vector<double> bar(foo, foo + 5);
    string temp = ContainerToString(bar.begin(), bar.end(), NumberConverter<double>());
    cout << temp;
    cin.get();
    return 0;
}

Thanks for taking your time to read this. All suggestions/criticisms are welcome.

Nathan

> In my code I just wrote one for numbers.
Good news! The NumberConverter actually works for any type with an overloaded << operator. Beyond recommending more of the STL and Boost, Edward approves of your code. ;)

I take it that it works that way because it is a stream and all we are doing with string stream conversions in outputting to the stream and then inputting it back into a variable? Thanks for the approval. Its always nice to have someone like what I have written.

> I take it that it works that way because it is a stream
And the fact that the type of the object being converted is defined with a template parameter. Boost::lexical_cast<> works the same way.

First make use of const. Second in your NumberToString function, what happens
when the conversion fails? When the stringstream cannot convert the object passed in?
Handle those extreme cases as well. Also in your ContainerToString function, you
could just as well use a forward Iterator and it would work.

@ first person Thanks for your suggestions.
I added constant declarers to what I think should be const. I also changed the conversion function's name to StringStreamConverter so It looks better since as Ed pointed out any type with an operator <<() can be used with this method. I did add a check to see if the conversion returned null and if it does than it exits the function and returns a null string. Could you please show me a case where the conversion would fail with string streams that wouldn't be caught by the compiler during compiling? Here is where I am now and thanks for the help guys.

// ContainerToString.h

#ifndef CONTAINERTOSTRING_H
#define CONTAINERTOSTRING_H

#include <string>
#include <functional>
#include <sstream>

// simple string stream conversion.  supports any type with an operator<<() function.
template<typename T>
class StringStreamConverter : std::unary_function<T, std::string>
{
public:
    std::string operator()(const T input) const
    {
        std::string output;
        std::stringstream ss;
        ss << input;
        getline(ss, output);
        return output;
    }
};

// builds the entire string from the contianer and returns the final string
// returns a null string on error
template <typename ForwardIterator, typename ConvertFunction>
std::string ContainerToString(const ForwardIterator first, const ForwardIterator last, ConvertFunction function)
{
    ForwardIterator start = first;  // did this because you cant use ++ on first now.
    std::string builder;
    std::string temp;
    while (start != last)
    {
        temp = function(*start) + " ";
        if (temp.empty())
            return string("");
        builder += temp;
        start++;
    }
    builder.erase(builder.size() - 1, 1);
    return builder;
}

#endif
// Main.cpp
#include <iostream>
#include <string>
#include <vector>
#include "ContainerToString.h"

using namespace std;

int main()
{
    double foo[] = {10.1, 20.2, 30.3, 40.4, 50.5};
    vector<double> bar(foo, foo + 5);
    vector<double>::const_iterator it = bar.begin(), end = bar.end();
    string temp = ContainerToString(it, end, StringStreamConverter<double>());
    cout << temp;
    cin.get();
    return 0;
}

I believe you may be somewhat re-inventing the wheel here - you can achieve the same result using a std::stringstream, an ostream_iterator and the std::copy algorithm :-)

#include <string>
#include <algorithm>
#include <iterator>
#include <sstream>

template<typename FwdIt>
std::string to_string(FwdIt begin, FwdIt end)
{
    //retrieve the value type for any kind of iterator, including a "raw" pointer
    typedef std::iterator_traits<FwdIt>::value_type value_type;

    std::ostringstream buffer;
    std::ostream_iterator<value_type> buffer_inserter(buffer, " ");
    std::copy(begin, end, buffer_inserter);
    return buffer.str();
}

main/test:

#include <iostream>
#include <vector>

template<typename T, int N> 
T* end_of(T (&arr)[N])
{ return arr+N; }

int main()
{
    double foo[] = { 10.1, 20.2, 30.3, 40.4, 50.5 };
    std::vector<double> bar(foo, end_of(foo));
    std::cout << to_string(bar.begin(), bar.end());
}
commented: good example +3

I didn't compile your program bench, but in some compiler you may need to use the
keyword, typename in your to_string function, specifically here

typedef std::iterator_traits<FwdIt>::value_type value_type

because the value_type depends on the type that is passed. So the line would be :

typedef typename std::iterator_traits<FwdIt>::value_type value_type

I didn't compile your program bench, but in some compiler you may need to use the
keyword, typename in your to_string function, specifically here

typedef std::iterator_traits<FwdIt>::value_type value_type

Well spotted - VS2008 didn't pick up on that even after I switched off MS-specific extensions, however if I use the same typedef in a class/struct, then it correctly throws out an error. I guess I should rely on the comeau compiler more often!

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.