2

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++.

rsaavedra
  • 378
  • 1
  • 4
  • 12
  • 1
    Read line, parse field 1 using if-else if - else if and else = error to choose type of field 2. That's safer than assuming every line has valid data. – Dave S May 15 '19 at 01:36
  • Why writing such code your self? Use a serializer for it. The intend is simply to read/write data with all kind of types from any stream. https://uscilab.github.io/cereal/ https://www.boost.org/doc/libs/1_70_0/libs/serialization/doc/index.html – Klaus May 15 '19 at 08:17

2 Answers2

2

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.

Fareanor
  • 5,900
  • 2
  • 11
  • 37
  • This is exactly what I wanted. In `void read_val(const std::string & data, T & val);` why are you referencing the string data? – rsaavedra May 15 '19 at 15:55
  • 1
    I pass the data by const reference because it avoids useless copy and so, is more efficient. – Fareanor May 15 '19 at 18:06
1

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;
}
Federico
  • 743
  • 9
  • 22