I have a class called Token
which has an attribute called valor
that stores the value of the token and an attribute tipo
that indicates what type it is. What this class does more than anything is classify a string that has already been parsed. For example, the string "hola"
is classified as STRING
and the stored value is hola
.
My problem occurs when comparing Tokens, because for any type of comparison I would need to cast or convert the stored string to the corresponding data type. It occurred to me to use the module any
for this but it would have to have a series of if
to do the corresponding validation of each token.
My class is like this:
#include <any>
#include <iostream>
#include <string>
class Token {
private:
string valor;
int tipo, num_linea;
public:
Token() = default;
Token(bool tk, int linea): valor(string(tk ? "verdadero": "falso")), num_linea(linea){}
Token(string tk, int linea){
valor = tk;
if (tk == "=") tipo = ASIGNACION;
else if (tk == ";") tipo = END;
else if (tk[0] == '"' && tk[tk.size() - 1] == '"') {
tipo = STRING;
valor = tk.substr(1, tk.size() - 2);
}
else if (tk.size() == 1 && Operadores.find(tk)) tipo = OPERADOR;
else if (tk == "NADA") tipo = NADA;
else if (tk == "verdadero" || tk == "falso") tipo = BOOL;
else if (esEntero(tk)) tipo = ENTERO;
else if (!isdigit(tk[0]) && validIdentificador(tk)) tipo = IDENTIFICADOR;
else throw TokenError(linea);
num_linea = linea;
}
int getTipo() { return tipo; }
string getValor() { return valor; }
int getLinea() { return num_linea; }
Token operator ==(Token& tk) {
if(tk.getTipo() != tipo ) return Token(false, num_linea);
// no se como castear al tipo correspondiente
auto val = any_cast<int>(tk.getValor());
return Token(tk.getValor() == valor, num_linea);
};
friend ostream& operator <<(ostream& os, const Token& tk){
os << tk.valor;
return os;
}
};
The data types are constants and are defined in another file, an example would be #define ENTERO 1
==
As you can see in the overload of the use operator any_cast
to transform the string to an integer, but to make it fully functional and to detect the other types, it should use a series of if
to validate each token (the one that is received by parameter and the current one that would be this
). A small implementation would be:
Token operator ==(Token& tk) {
if(tk.getTipo() != tipo ) return Token(false, num_linea);
if(tipo == ENTERO) return Token(any_cast<int>(tk.getValor()) == any_cast<int>(valor), num_linea);
if(tipo == STRING) return Token(tk.getValor() == valor, num_linea);
if(tipo == BOOL) return Token(tk.getValor() == valor, num_linea);
}
And you would have to do this on all operator overloads you have or wherever you need to do the conversion. My idea is to have another method for this and return the data already cast, but I can't do it. I tried creating a function and specifying its return data type as any
, which didn't work that well for me, since it returns a data but I still need to know the type to do the casting.
What I did was something like this:
...
private:
any parse(){
if(tipo == STRING) return string(valor);
else if (tipo == ENTERO) return atoi(valor.c_str());
}
...
But as I mentioned, my problem would come when doing any_cast
the result, because I need to know the data type again. How could I implement a method that returns the already converted value?
Perhaps the most common way to solve this problem is by resorting to the use of templates and overloading.
Some reasons to justify this approach could be
Think how complicated this function would be if you had to implement conversions for integers (signed and unsigned and with different size in bytes), floating point numbers, boolean and... custom types?
With templates and overloading, on the other hand, you get a mechanism that is equal parts flexible and extensible.
So, for example, you could define the function
parse
such that:What this function does is simply delegate the conversion to a callable element called
token_parser
, which is also a template.This element is the one that will be in charge of performing all the magic.
At first
token_parser
it will be a function, then the first thing we are going to do is declare it:Next, you are going to implement all the conversions you need:
If you try to do a conversion that is not implemented, the build will simply fail
You can see the working example here