I have some kind of Schrödinger variable

Viewed 172

I'm creating a lexer and I need to compute things. I created my compute function which takes three arguments: the two strings value and the operator. For instance when I do compute("34","63","+") it returns "94".

But when comes the time to compute I first have to convert some values into int or float:

    if(is_int(val1)){
        int value1 = convert_to_int(val1);
    }else if(is_float(val1)){
        float value1 = convert_to_float(val1);
    }else if(is_string(val1)){
        string value1 = GetFrom(1,val1.size()-1,val1);
    }else string value1 = val1;
    if(is_int(val2)){
        int value2 = convert_to_int(val2);
    }else if(is_float(val2)){
        float value1 = convert_to_float(val2);
    }else if(is_string(val2)){
        string value2 = GetFrom(1,val2.size()-1,val2);
    }else string value2 = val2;

(opera stands for operator)

If it it not either float or int it stays string. But g++ does not appreciate the facts I use a variable and he does not know what it should be.

I tried declaring the variable before (int value1; int value2;) but when I uses functions which takes string arguments with a pseudo-int variable it does not work. I also tried using std::any but anyway.

So I have reached the point when I have to ask my question: is there a way to tell g++ to do not care about this specific error?

2 Answers

What you are looking for is called a variant or a tagged union/algebraic sum type.

It goes by many names, but the pattern of "represent a single value that could be 1 of N types" is very common in programming languages and is usually hard-coded into variables (Python, Ruby, Lisp, JavaScript, etc.) or represented through an explicit union/enum/sum type (C++, C, Rust, Java, C#, etc.).

If you can use C++17 and later, you can just use std::variant<int, float, std::string> to do as you please, and then use std::get<T>, std::holds_alternative<T>, and std::visit<R, Visit, ...Variants> to generically query, get, and perform actions depending on what type of variant it is.

The (very rough and first-draft) code below should work if both of the variant values have the same type contained within them, but still fail for other cases. I have also taken the liberty to provide some dummy implementations for the functions you used but did not define:

#include <variant>
#include <string>
#include <iostream>

#include <stdio.h>

using namespace std;

bool is_int(const string& s) {
    try {
        stoi(s);
        return true;
    } catch (invalid_argument e) {
        return false;
    }
};

bool is_float(const string& s){
    try {
        stof(s);
        return true;
    } catch (invalid_argument e) {
        return false;
    }
};

bool is_string(const string& s){
    return true;
};

string GetFrom(size_t first_index, size_t last_index, const string& s){
    return s.substr(first_index, last_index-first_index);
};

int convert_to_int(const string& s){
    return stoi(s);
}

float convert_to_float(const string& s) {
    return stof(s);
}

int main() {
    variant<int, float, string> value1, value2;

    string val1 = "123.43";
    string val2 = "4566.123";

    if (is_int(val1))  {
        value1 = convert_to_int(val1);
    } else if(is_float(val1)) {
        value1 = convert_to_float(val1);
    } else {
        value1 = GetFrom(1,val1.size()-1,val1);
    }

    if (is_int(val2)) {
        value2 = convert_to_int(val2);
    } else if (is_float(val2)) {
        value2 = convert_to_float(val2);
    } else {
        value2 = GetFrom(1,val2.size()-1,val2);
    }

    variant<int, float, string> result;

    if (holds_alternative<int>(value1) && holds_alternative<int>(value2)) {
        result = get<int>(value1) + get<int>(value2);
    } else if (holds_alternative<float>(value1) && holds_alternative<float>(value2)) {
        result = get<float>(value1) + get<float>(value2);
    } else {
        result = get<string>(value1) + get<string>(value2);
    }
    
    cout << holds_alternative<int>(value1) << endl;
    

    visit([](const auto& val) {
        cout << val << endl;
    }, result);

    return 0;
}

NOTE: your strategy of checking for variable type from a string is probably best served by taking advantage of regular expressions (regex), which are tailor-made and theoretically sound to perform the string-matching you desire. Most professional applications will use some sort of regex or context-free parsing algorithm to parse simple types of strings or infinitely-nestable strings.

First, we define Value that can hold values with a type tag:

using Value = std::variant<int, float, std::string>;

We can then define a function that goes from operand to Value:

Value from_string(std::string v) {
  if (is_int(v))
    return Value{std::in_place_type_t<int>{}, convert_to_int(v)};
  if (is_float(v))
    return Value{std::in_place_type_t<float>{}, convert_to_float(v)};
  if (is_string(v))
    return Value{GetFrom(1,v.size()-1,v)};
  return Value{}; // TODO: handle this case.
}

And implement the plus operator as follows. As you can see it is very verbose as it needs to check 3^2 types.

Value plus(const Value v1, const Value v2) {
  if (auto v1_int = std::get_if<int>(v1)) {
    if (auto v2_int = std::get_if<int>(v2))
      return Value{std::in_place_type_t<int>{}, *v1_int + *v2_int};
    if (auto v2_float = std::get_if<float>(v2))
      return Value{std::in_place_type_t<float>{}, *v1_int + *v2_float};
    if (auto v2_string = std::get_if<std::string>(v2))
      return Value{std::to_string(*v1_int + *v2_string)};
  } else 

  if (auto v1_float = std::get_if<float>(v1)) {
    if (auto v2_int = std::get_if<int>(v2))
      return Value{std::in_place_type_t<int>{}, *v1_float + *v2_int};
    if (auto v2_float = std::get_if<float>(v2))
      return Value{std::in_place_type_t<float>{}, *v1_float + *v2_float};
    if (auto v2_string = std::get_if<std::string>(v2))
      return Value{std::to_string(*v1_float + *v2_string)};
  } else

  if (auto v1_string = std::get_if<std::string>(v1)) {
    if (auto v2_int = std::get_if<int>(v2))
      return Value{*v1_string + std::to_string(*v2_int)};
    if (auto v2_float = std::get_if<float>(v2))
      return Value{*v1_string + std::to_string(*v2_float)};
    if (auto v2_string = std::get_if<std::string>(v2))
      return Value{*v1_string + *v2_string};
  }

  return Value{};
}
Related