I'm wondering if there is an elegant way of writing a single function that reads list of numbers (int or double) into a vector using a templated function?
Here is what I usually do:
template<class VecType>
vector<VecType> read_vector(const string& file){
vector<VecType> vec;
ifstream indata;
indata.open(file);
string line;
while (getline(indata, line)) {
stringstream lineStream(line);
string cell;
while (std::getline(lineStream, cell, ',')) {
vec.push_back(stod(cell));
}
}
indata.close();
return vec;
}
My problem is with the stoi or stod part. How to handle this nicely here?
What I usually do, is to use stod and let the conversion happen automatically from double to int if the VecType is int for example. But there should be much better way to do this, right?
You could have specialized template:
template <class T> T from_string(const std::string&);
template <> int from_string<int>(const std::string& s) { return stoi(s); }
template <> double from_string<double>(const std::string& s) { return stod(s); }
and use vec.push_back(from_string<VecType>(cell));
Related
I want to a program to read strings like:
integer_value 1
double_value 1.0
string_value one
I implement the following functions in order to read these:
void read_val_int(
std::vector<std::string> str_vec,
std::string str,
int& val){
if(str_vec[0]==str) val= std::stoi(str_vec[1]);
}
void read_val_dbl(
std::vector<std::string> str_vec,
std::string str,
double& val){
if(str_vec[0]==str) val= std::stoi(str_vec[1]);
}
void read_val_str(
std::vector<std::string> str_vec,
std::string str,
std::string& val){
if(str_vec[0]==str) val= str_vec[1];
}
str_vec is a vector containing two string values, e.g. {"integer_value","1"}.
str contains a string I want to compare with str_vec[0]
val is an integer, double or string that corresponds to str_vec[1] in case str_vec[0]==str is true.
I use these functions as, e.g. read_val_int(my_str_vec,"integer_value",my_int_val).
My question is: Is there a way of using one single function in order to do this? I have tried using a template but since I need to reference val this seems impossible.
Note: I'm aware of this post but it is in C and seems kinda messy to me. Maybe there is a simpler way to achieve this in C++.
If you are before C++17 and so cannot use std::variant, you can use only one function by using templates.
You declare the function as follows:
template <typename T>
void read_val(const std::string & data, T & val);
Then you specialize it for your three types:
template <>
void read_val<int>(const std::string & data, int & val)
{
val = std::stoi(data);
}
template <>
void read_val<double>(const std::string & data, double & val)
{
val = std::stod(data);
}
template <>
void read_val<std::string>(const std::string & data, std::string & val)
{
val = data;
}
And the job is done, you can use the function for you three types by calling one and only one function: read_val().
You can use it as follows:
std::string data_int("5");
std::string data_double("2.5");
std::string data_string("Hello");
int int_val;
double double_val;
std::string string_val;
read_val(data_int, int_val);
read_val(data_double, double_val);
read_val(data_string, string_val);
std::cout << int_val << std::endl;
std::cout << double_val << std::endl;
std::cout << string_val << std::endl;
As you can see, by the use of template specialization, you can use the same function for different types.
Moreover, it will automatically assure you that an allowed type is passed. Indeed, if you give something else than an int, double or std::string to the function, the compilation will fail because there is no specialization for it.
I hope it helps.
As suggested in Dave's comment, you should check the type of your variable parsing the first element of the vector.
Inside the if-else chain you can what you need with the right type of your variable.
You could also have a single function to return your values using std::variant e then printing values (or do whatever you need) using c++17 std::visit.
It could be something like this:
#include <vector>
#include <string>
#include <variant>
#include <iostream>
using my_variant = std::variant<int, double, std::string>;
my_variant read_val(
const std::vector<std::string> &str_vec)
{
if(str_vec[0]=="integer_value")
{
return std::stoi(str_vec[1]);
}
else if(str_vec[0]=="double_value")
{
return std::stod(str_vec[1]);
}
else if(str_vec[0]=="string_value")
{
return str_vec[1];
}
//notify error in some way, maybe throw
}
void print_variant(const my_variant &v)
{
std::visit([](my_variant &&var)
{
if (std::holds_alternative<int>(var))
std::cout<<"int->"<<std::get<int>(var)<<"\n";
else if (std::holds_alternative<double>(var))
std::cout<<"double->"<<std::get<double>(var)<<"\n";
else if (std::holds_alternative<std::string>(var))
std::cout<<"string->"<<std::get<std::string>(var)<<"\n";
}, v);
}
int main()
{
std::vector<std::string> vec_int {"integer_value", "1"};
std::vector<std::string> vec_dbl {"double_value", "1.5"};
std::vector<std::string> vec_str {"string_value", "str"};
print_variant(read_val(vec_int));
print_variant(read_val(vec_dbl));
print_variant(read_val(vec_str));
return 0;
}
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);
Currently I am working on a function similar to the String.Format(...) function from C#, just in C++. (String.Format(...))
But that's not my problem. The function works fine but problematic is that it takes a vector<string> as parameter and if I want to use an integer as parameter, I must write code like this:
// function prototype, the function body is not relevant here
string format(string str, vector<string> variables);
// ... some context
// i could use to_string() here,
// but imagine a complex type which only overrides the stream operator
int a = 20;
stringstream ss;
ss << a;
string a_str = format("a has the value '{}'", { ss.str() });
That's quite some boilerplate code!
Thus I need a function which converts a collection of unknown data types into a vector<string>.
I tried a few things like this:
vector<string> vec_string(vector<void*> args) {
vector <string> result;
for (unsigned i = 0; i < args.size(); i++)
{
stringstream ss;
// I can't dereference an object without knowing to pointer type. :(
ss << *((int*)args[i]);
result.push_back(ss.str());
}
return result;
}
// ... some context
int a = 10;
cout << format("some int: '{}'", vec_string({ (void*) &a }));
Which obviously only works for integer and is very uncomfortable. I feel like the only way to do this is a variadic macro but I got no idea how they work.
here is a link to my format(...) method.
I am sorry about my spelling, but I tried my best correcting it.
This can be done relatively easily with variadic templates:
template <class T>
auto toString(T&& t) {
std::stringstream s;
s << std::forward<T>(t);
return s.str();
}
template <class... T>
auto toStringVector(T&&... args) {
std::vector<std::string> res {toString(std::forward<T>(args))...};
return res;
}
This will convert each parameter to std::string via a stringstream and then return an std::vector<std::string> containing said strings. (Live example.)
You can then use this straight forward as intended in the question, that is:
std::cout << format("some text", toStringVector(any, number, of, arguments,
of, any, type));
If you are using Boost, you can skip the toString helper in favor of boost::lexical_cast:
template <class... T>
auto toStringVector(T&&... args) {
std::vector<std::string> res { boost::lexical_cast<std::string>(std::forward<T>(args))...};
return res;
}
The lexical_cast will most likely be faster on built-in types.
I figured it out, no idea how I did that on the first try - without compiler errors, but here is how I did it:
// function prototype, the function body is not relevant here
string format(string str, vector<string> variables);
template <class T>
vector<string> paramsToString(vector<string> vec, T last) {
stringstream ss;
ss << last;
vec.push_back(ss.str());
return vec;
}
template <class T, class ... REST>
vector<string> paramsToString(vector<string> vec, T next, REST ... rest) {
stringstream ss;
ss << next;
vec.push_back(ss.str());
return paramsToString(vec, rest...);
}
template <class ... ARGS>
vector<string> paramsToString(ARGS ... args) {
return paramsToString(vector<string>(), args ...);
}
// ... some context
// ComplexType overrides the stream operator.
cout << format("an int: '{0}', and string: '{1}' and some other type: '{2}'",
paramsToString(10, "Hello World", ComplexType(10)));
And it works! Even with custom types. Amazing!
Thank You guys for your help!
I don't want just the first one, I want them all. How can I do this? I always get either nothing or the first value only and I've been converting the stringstream into a string. Is there a way to get it directly from stringstream? The ints are separated by semicolons.
You can use std::vector, and, if you know the number of ints before-hand, you could use reserve to improve performance. Since you're using semi-colons as delimiters, one solution would be to use this delimiter with std::getline:
#include <iostream>
#include <sstream>
#include <vector>
#include <string>
int main()
{
std::stringstream ss{"1;2;3;4;5;6;7;8;9;10"};
std::vector<int> ints;
// Reserve space for 10 integers:
ints.reserve(10);
std::string temp;
while (std::getline(ss, temp, ';'))
ints.push_back(std::stoi(temp));
for (int i : ints)
std::cout << i << ' ';
return 0;
}
Thats just simply using copy and iteratrs.
template<std::size_t SIZE>
void func(std::istream& stream, std::array<int, SIZE>& arr)
{
std::copy_n(std::istream_iterator<int>(stream), SIZE, std::begin(arr));
}
But rater than thinking about arrays you should think about using iterator. That way you can use any container types.
template<typename I>
void func(std::istream& stream, I dst)
{
std::copy(std::istream_iterator<int>(stream),
std::istream_iterator<int>(),
dst);
}
You also mention vector in the comments:
void func(std::istream& stream, std::vector<int>& vec)
{
std::copy(std::istream_iterator<int>(stream),
std::istream_iterator<int>(),
std::back_inserter(vector)
);
}
Now you mention that your numbers are comma seporated. To cope with this you can defined a class that reads a number and a trailing comma;
struct NumberAndComma
{
int number;
operator int() {return number;}
friend std::istream& operator>>(std::istream& str, NumberAndComma& data)
{
if (str >> number) {
char x;
stream >> x;
if (x != ',') {
stream.unget();
}
if (!stream) {
// It was good before we tried to read comma.
// so really it should still be good even if there is no comma
stream.clear();
}
}
return stream;
}
};
Now you can use this inplace of int above:
void func(std::istream& stream, std::vector<int>& vec)
{
std::copy(std::istream_iterator<NumberAndComma>(stream),
std::istream_iterator<NumberAndComma>(),
std::back_inserter(vector)
);
}
Probably another dumb question that results from my studying in bad C++ book (I intend to correct this).
I was playing with sstream and tried the following function:
template <class num> num fromString(const std::string& str) {
std::istringstream ss(str);
num temp;
ss >> temp;
return temp;
}
When I call it like:
int i = fromString<int>("123");
it works ok. But if I call it like:
int i = fromString("123");
I got a compiling error:
error: no matching function for call to ‘fromString(std::string&)’
I thought the compiler would understand that if I'm assigning the value to a int then I must be talking about fromString<int>, but it seems it's not the case.
Am I missing something? Should I always specify the type of a templated function? Or just when the template type is the return type? Or just when the template type can't be determined by the types of the inputs?
No, the compiler cannot deduce template parameters from return type alone. You have to be specific as you were originally:
int i = fromString<int>("123");
One of the reasons why the compiler cannot deduce in this case is because a number of types could be convertible to int. For instance:
class Gizmo
{
public:
operator int() const;
};
How would the compiler know not to instantiate fromString<Gizmo>() in this case?
You can get this effect with a simple trick.
#include <sstream>
#include <string>
using namespace std;
struct from_string {
string str;
from_string(const string& s) : str(s) { }
template <typename T>
operator T() const {
T ret;
stringstream ss(str);
ss >> ret;
return ret;
}
}
int main() {
string s("1234");
int a = from_string(s);
return 0;
}
In addition to what John said about this being what is called a 'non-deducible context', you can sometimes get around things like this by providing a dummy parameter of the template type:
#include <sstream>
#include <string>
template <typename T>
T from_string(const std::string& s, const T& /*dummy*/)
{
std::stringstream ss;
ss << s;
T t;
ss >> t;
return t;
}
int main()
{
std::string s = "23";
int i = from_string(s, i);
return 0;
}
That helps the compiler to deduce the type and avoids you having to explicitly specify it. It's particularly useful when the name of the variable is nice and short, as in the example above.