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?
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", ",");
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.
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.
Related
I'm using a very nice and simple std::vector<std::string> initializer which takes an input string and regex. It's similar to a basic split, just it works with regex Group1 matches:
static std::vector<std::string> match(const std::string& str, const std::regex& re) {
return { std::sregex_token_iterator(str.begin(), str.end(), re, 1), std::sregex_token_iterator() };
}
Construction of a vector is done like below:
std::string input = "aaa(item0,param0);bbb(item1,param1);cc(item2,param2);";
std::vector<std::string> myVector = match(input, std::regex(R"(\(([^,]*),)"));
This results a vector containing item0,item1,item2 extracted from an input string with regex:
Now my match function uses the first group results of the regex and (I believe) utilizes the vector's intialization form of:
std::vector<std::string> myVector = { ... };
I'd like to create a similar match function to construct a std::map<std::string,std::string>. Map also has the above initializator:
std::map<std::string,std::string> myMap = { {...}, {...} };
My idea is to modify the regex to create more group results:
And I would like to modify the above match function to create a nice map for me with the modified regex (\(([^,]*),([^)]*)), resulting the same as this:
std::map<std::string,std::string> myMap = { {"item0", "param0"}, {"item1", "param "}, {"item2", "param2"}, };
What I've tried?
static std::map<std::string, std::string> match(const std::string& str, const std::regex& re) {
return { std::sregex_token_iterator(str.begin(), str.end(), re, {1,2}), std::sregex_token_iterator() };
}
This one (in case of a vector) would put both Group1 and Group2 results into the vector. But it can not initialize a map.
How can I still do that easily (Is it not possible with sregex_token_iterator)?
I don't know what 'easily' does mean exactly, so here comes simple solution:
#include <iostream>
#include <regex>
#include <vector>
static std::map<std::string, std::string> match(const std::string& str, const std::regex& re) {
std::map<std::string, std::string> retVal;
auto token = std::sregex_token_iterator(str.begin(), str.end(), re, {1,2});
for (auto it=token++, jt=token; it != std::sregex_token_iterator(); ++it, jt = it++)
retVal.emplace(*it,*jt);
return retVal;
}
int main() {
std::string input = "aaa(item0,param0);bbb(item1,param1);cc(item2,param2);";
auto myVector = match(input, std::regex(R"(\(([^,]*),([^)]*))"));
for (const auto& item : myVector)
std::cout<<item.first<<'\t'<<item.second<<std::endl;
}
You can also could try to use boost and homemade generic algorithm.
I read a text file which contains lines each line contains data separated by delimiters like spaces or commas , I have a function which split a string to array but I want to make it a template to get different types like floats or integers beside string, I made two functions one for split to strings and the the other to floats
template<class T>
void split(const std::string &s, char delim, std::vector<T>& result) {
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
T f = static_cast<T>(item.c_str());
result.push_back(f);
}
}
void fSplit(const std::string &s, char delim, std::vector<GLfloat>& result) {
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
GLfloat f = atof(item.c_str());
result.push_back(f);
}
}
the template function works fine with strings, in the other function I use atof(item.c_str()) to get the float value from string, when I use the template function with floats I get invalid cast from type 'const char*' to type 'float'
so how could I make the casting in the template function?
You cannot do:
T f = static_cast<T>(item.c_str());
In your case, you could declare a template, e.g. from_string<T>, and replace the line with:
T f = from_string<T>(item);
The you would implement it with something like:
// Header
template<typename T>
T from_string(const std::string &str);
// Implementations
template<>
int from_string(const std::string &str)
{
return std::stoi(str);
}
template<>
double from_string(const std::string &str)
{
return std::stod(str);
}
// Add implementations for all the types that you want to support...
You could use the strtof function (http://en.cppreference.com/w/cpp/string/byte/strtof)
so something like this
GLfloat f = std::strtof (item.c_str(), nullptr);
I used this approach to create a case-insensitive typedef for string. Now, I'm trying to convert a std::string to ci_string. All of the following throw compiler errors:
std::string s {"a"};
ci_string cis {s};
ci_string cis (s);
ci_string cis {(ci_string)s};
ci_string cis ((ci_string)s);
ci_string cis = s;
I spent some time trying to figure out how to overload the = operator, and I attempted to use static_cast and dynamic_cast without success. How can I do this?
Your two types are different, so you cannot use the constructor with a regular std::string. But your string is still able to copy a C string, so this should work:
std::string s{"a"};
ci_string cis{ s.data() }; // or s.c_str(), they are the same
std::string and ci_string are unrelated types. Why would static_cast or dynamic_cast be able to convert them? Remember: Two different instantiations of the same template are unrelated types and are potentially completely incompatible.
Give up on the idea of overloading operator= or on some magic that performs the conversion automatically. You have two unrelated types. But they both offer member functions that can you can successfully use to copy the char elements from one to the other.
Just write a simple conversion function that takes advantage of the fact that both std::string and ci_string have their value_type defined as char, and appropriately use one of std::basic_string's constructors, either one which takes a pointer to raw data or one which takes two iterators which form a range.
Here is a complete example:
#include <string>
#include <iostream>
struct ci_char_traits : public std::char_traits<char> {
static bool eq(char c1, char c2) { return toupper(c1) == toupper(c2); }
static bool ne(char c1, char c2) { return toupper(c1) != toupper(c2); }
static bool lt(char c1, char c2) { return toupper(c1) < toupper(c2); }
static int compare(const char* s1, const char* s2, size_t n) {
while( n-- != 0 ) {
if( toupper(*s1) < toupper(*s2) ) return -1;
if( toupper(*s1) > toupper(*s2) ) return 1;
++s1; ++s2;
}
return 0;
}
static const char* find(const char* s, int n, char a) {
while( n-- > 0 && toupper(*s) != toupper(a) ) {
++s;
}
return s;
}
};
typedef std::basic_string<char, ci_char_traits> ci_string;
ci_string to_ci_string(std::string const& src)
{
return ci_string(src.begin(), src.end());
// or:
// return ci_string(src.c_str());
}
int main()
{
std::string s {"a"};
auto cis = to_ci_string(s);
std::cout << cis.c_str() << "\n";
}
I have a function with the following signature:
std::string f(const char *first, const char *last) {
std::string result;
std::for_each(first, last, some_lambda_which_appends_to_result);
return result;
}
and an overload for std::string which calls it:
std::string f(const std::string s) {
return f(&*s.begin(), &*s.end());
// The one below would assume that the string is not empty
// f(& s.front(), & s.front() + s.size());
}
However, this may be unsafe (dereferencing s.end() might be a red card offense in itself).
Is there a safe way to get a pointer to the beginning of characters and a one-past-the-end pointer (two null pointers would be fine in case of an empty string),
or do I have to write
std::string(const std::string& s) {
return s.empty() ? std::string() : f(& s.front(), & s.front() + s.size());
}
It's not safe to dereference end(). However, you can use either c_str() or data() to achieve what you need:
std::string(const std::string& s) {
return f(s.data(), s.data() + s.size());
}
I was writing out the function below, and started to think that there's probably a better way to go about it; however Google isn't turning up much, so any insight would be appreciated. I also have a very similar situation involving integers.
bool compare_strs (std::string operator_, std::string str_0, std::string str_1)
{
if (operator_ == ">")
{
return str_0 > str1;
}
else if (operator_ == "<")
{
return str_0 < str1;
}
else if (operator_ == "<=")
{
return str_0 <= str1;
}
else
{
return str_0 >= str1;
}
}
You can use a map to store operators and related functors. In C++11, something along these lines should work, though there might be a couple subtle errors. In C++03, you'll have to change a couple things, including changing std::function to boost::function or function pointers, as well as using std::make_pair to store the map values.
#include <functional> //for std::function and std::less et al.
#include <map> //for std::map
#include <stdexcept> //for std::invalid_argument
#include <string> //for std::string
struct StringComparer {
static bool compare( //split up to fit width
const std::string &oper,
const std::string &str0, const std::string &str1
) {
MapType::const_iterator iter = operations.find(oper);
if (iter == std::end(operations)) //check if operator is found
throw std::invalid_argument("No match for provided operator.");
return iter->second(str0, str1); //call the appropriate functor
}
private:
using MapType = std::map< //makes life easier, same as typedef
std::string,
std::function<bool(const std::string &, const std::string &)>
>;
static const MapType operations; //a map of operators to functors
};
const StringComparer::MapType StringComparer::operations = { //define the map
{"<", std::less<std::string>()}, //std::less is a functor version of <
{"<=", std::less_equal<std::string>()},
{">", std::greater<std::string>()},
{">=", std::greater_equal<std::string>()}
};
You can also see it in action. The nice thing about an approach like this is that it's very easy to include more operators, as all you have to do is add them to the map.
As others have mentioned, you should first ask yourself why you are doing this - there is likely a better solution. Going with this though, I might do something like:
template <typename T1, typename T2>
bool mycompare(std::string operator_, const T1 & _lhs, const T2 & _rhs)
{
if (operator_ == ">")
{
return _lhs > _rhs;
}
else if (operator_ == "<")
{
return _lhs < _rhs;
}
//etc.
else
{
throw new exception("Invalid operator");
}
}