Brett
Brett

Reputation: 12007

C++ - How to convert a function to a template function

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?

Upvotes: 2

Views: 1067

Answers (3)

Shoe
Shoe

Reputation: 76240

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", ",");

Upvotes: 5

Praetorian
Praetorian

Reputation: 109119

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.

Upvotes: 4

Wojtek Surowka
Wojtek Surowka

Reputation: 20993

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.

Upvotes: 5

Related Questions