Reputation: 12007
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 int
s. 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
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
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
Reputation: 20993
You can "templatise" this function - to start it you just need to replace std::vector<std::string>
with 'std::vectorand add
template` 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