2

I have a C++ function that takes a comma separated string and splits in a std::vector<std::string>:

std::vector<std::string> split(const std::string& s, const std::string& delim, const bool keep_empty = true) {

    std::vector<std::string> result;
    if (delim.empty()) {
        result.push_back(s);
        return result;
    }
    std::string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = std::search(substart, s.end(), delim.begin(), delim.end());
        std::string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(temp);
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

However, I would really like to be able to apply this function to mutiple datatypes. For instance, if I have the input std::string:

1,2,3,4,5,6

then I'd like the output of the function to be a vector of ints. I'm fairly new to C++, but I know there are something called template types, right? Would this be possible to create this function as a generic template? Or am I misunderstanding how template functions work?

Shoe
  • 74,840
  • 36
  • 166
  • 272
Brett
  • 11,637
  • 34
  • 127
  • 213
  • 1
    Yes that works with a template. Create a function that gets a string and returns the template type. Now you can spezialize this function with different types. Do nothing for string, use atoi for int,... – dari Jul 14 '14 at 21:55
  • You've screwed up the question. SO doesn't work like this. What impression is the future reader going to have when there's a question and then another one in the update that refers to an answer to the first question? – 101010 Jul 14 '14 at 22:11
  • @40two Well, the update was in response to an answer posted here. So same question really IMO. :-) – Brett Jul 14 '14 at 22:12

3 Answers3

5

You can declare the template function as:

template<class ReturnType>
std::vector<ReturnType> split(const std::string&, const std::string&, const bool = true);

and then specialize it for every vector type you want to allow:

template<>
std::vector<std::string> split(const std::string& s, const std::string& delim, const bool keep_empty) {
    // normal string vector implementation
}

template<>
std::vector<int> split(const std::string& s, const std::string& delim, const bool keep_empty) {
    // code for converting string to int
}

// ...

You can read about string to int conversion here.

You will then need to call split as:

auto vec = split<int>("1,2,3,4", ",");
Community
  • 1
  • 1
Shoe
  • 74,840
  • 36
  • 166
  • 272
  • This looks super promising. I'm pretty new to this though, so I'm having trouble figuring out where/how to put these functions in my code - especially in relation to the code I posted. Do I put the guts of my code above in the template function? And then do the `atio` only in the specialized `` function? – Brett Jul 14 '14 at 22:03
  • @Jefffery. I added some more code above to give a better understanding of what I'm having trouble with. – Brett Jul 14 '14 at 22:10
  • @Brett The function declaration (without definition) must come first. See [here](http://ideone.com/zyIAqS) or [here](http://ideone.com/1FcnTA). – Shoe Jul 14 '14 at 22:48
  • 2
    It's a good start. However with your solution, you have to replicate and adapt ALL the code for every type you want to handle. What's the benefit of this approach compared to traditional function overloading ? Isn't the question more about using templates in order to avoid this duplication ? – Christophe Jul 14 '14 at 22:53
5

You can "templatise" this function - to start it you just need to replace std::vector<std::string> with 'std::vectorand addtemplate` before the function. But you need to take care of how to put the strings into the resulting vector. In your current implementation you just have

result.push_back(temp);

because result is vector of strings, and temp is string. In the general case though it is not possible, and if you want to use this function with e.g. vector<int> this line will not compile. However this problem is easily solved with another function - template again - which will convert string to whatever type you want to use split with. Let's call this function convert:

template<typename T> T convert(const std::string& s);

Then you need to provide specialisations of this function for any type you need. For instance:

template<> std::string convert(const std::string& s) { return s; }
template<> int convert(const std::string& s) { return std::stoi(s); } 

In this way you do not need to specialise the entire function as the other answer suggests, only the part depending on the type. The same should be done for the line

result.push_back(s);

in the case without delimiters.

Wojtek Surowka
  • 20,535
  • 4
  • 44
  • 51
4

Your function can be generalized fairly easily to return a vector of an arbitrary type using Boost.LexicalCast. The only hiccup is this condition:

if (delim.empty()) {
    result.push_back(s);
    return result;
}

This only works right now because both the input and output types are std::string, but obviously cannot work if you're returning a vector containing a type other than std::string. Using boost::lexical_cast to perform such an invalid conversion will result in boost::bad_lexical_cast being thrown. So maybe you want to rethink that part, but otherwise the implementation is straightforward.

#include <boost/lexical_cast.hpp>

template<typename Result>
std::vector<Result> 
    split(const std::string& s, const std::string& delim, const bool keep_empty = true) 
{
    std::vector<Result> result;

    if (delim.empty()) {
        result.push_back(boost::lexical_cast<Result>(s));
        return result;
    }
    std::string::const_iterator substart = s.begin(), subend;
    while (true) {
        subend = std::search(substart, s.end(), delim.begin(), delim.end());
        std::string temp(substart, subend);
        if (keep_empty || !temp.empty()) {
            result.push_back(boost::lexical_cast<Result>(temp));
        }
        if (subend == s.end()) {
            break;
        }
        substart = subend + delim.size();
    }
    return result;
}

Basically, all I've done is made the result type a template parameter and replaced

result.push_back(x);

with

result.push_back(boost::lexical_cast<Result>(x));

If you cannot use Boost, take a look at this answer that shows how to convert a string to some other type using a stringstream.

Praetorian
  • 106,671
  • 19
  • 240
  • 328