Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flexibly convert between string, int, double for C++ Variant Class

Tags:

c++

I'm implementing a variant class (not using boost) and I'm wondering how you'd handle the case where you'd store any of string, integer, or double and automatically convert it accordingly to desired type through ToString(), ToInt(), or ToDouble().

For example,

Variant a = 7;
cout << "The answer is" + a.ToString() << endl; // should print "The answer is 7"
a = "7.4";
double &b = a.ToDouble();
b += 1;
cout << a.ToString() << endl; // should print 8.4

ToXXX functions should return the reference of the type that you want to convert to. Right now, I have the code where it can return the same type as it was initially assigned to( Variant a = Int(7); a.ToInt() works) and raise exception when the assigned type is different from the one that you want to convert to.

Sorry using boost isn't an option.

like image 995
JosephH Avatar asked Nov 17 '11 19:11

JosephH


4 Answers

Just to let know that C++17 will implement an std::variant to do this.

like image 152
Pedro Reis Avatar answered Oct 22 '22 18:10

Pedro Reis


    #include <string>
    #include <iostream>
    class Variant{
    public:
        Variant(){
            data.type = UNKOWN;
            data.intVal = 0;
        }
        Variant(int v){
            data.type = INT;
            data.intVal = v;
        }
        Variant(double v){
            data.type = DOUBLE;
            data.realVal = v;
        }
        Variant(std::string v){
            data.type = STRING;
            data.strVal = new std::string(v);
        }
            //the missing copy constructor
             Variant(Variant const& other)
             {
                *this = other;// redirect to the copy assignment
              }

        ~Variant(){
            if(STRING == data.type){
                delete data.strVal;
            }
        }

        Variant& operator = (const Variant& other){
            if(this != &other)
            {
                if(STRING == data.type){
                    delete data.strVal;
                }

                switch(other.data.type){
                case STRING:{
                        data.strVal = new std::string(*(other.data.strVal));
                        data.type = STRING;
                        break;
                    }
                default:{
                        memcpy(this, &other, sizeof(Variant));
                        break;
                    }           
                }
            }
            return *this;
        }
        Variant& operator = (int newVal){
            if(STRING == data.type){
                delete data.strVal;
            }
            data.type = INT;
            data.intVal= newVal;
            return *this;
        }

        Variant& operator = (double newVal){
            if(STRING == data.type){
                delete data.strVal;
            }
            data.type = DOUBLE;
            data.realVal= newVal;
            return *this;
        }

        Variant& operator = (std::string& newVal){
            if(STRING == data.type){
                delete data.strVal;
            }
            data.type = STRING;
            data.strVal= new std::string(newVal);
            return *this;
        }
        operator int&() {
            if(INT == data.type)
            {
                return data.intVal;
            }
            //do type conversion if you like
            throw std::runtime_error("bad cast");
        }

        operator double&()  {
            if(DOUBLE == data.type){
                return data.realVal;
            }
            //do type conversion if you like
            throw std::runtime_error("bad cast");
        }
        operator std::string&() {
            if(STRING == data.type){
                return *data.strVal;
            }
            throw std::runtime_error("bad cast");
        }
    private:
        enum Type{
            UNKOWN=0,
            INT,
            DOUBLE,
            STRING
        };
        struct{
            Type type;
            union 
            {
                int intVal;
                double realVal;
                std::string* strVal;
            };
        } data;
    };


    int main(){
        Variant v("this is string");//string
        v=1;//int
        v=1.0;//double
        v=std::string("another string");//
        Variant v2; //unkown type
        v2=v;//string
        std::cout << (std::string&)v2 << std::endl;
        return 0;
    }    
like image 45
BruceAdi Avatar answered Nov 12 '22 05:11

BruceAdi


To implement something like this, you need to be able to change the stored type, because if the user changes the variable by reference, he wants the change to affect the value stored, so I'd write something like this:

class Variant
{
private:
    enum StoreType
    {
        Integer,
        Float,
        String,
    }
    store_type;

    union
    {
       int * as_integer;
       double * as_double;
       std::string * as_string;
    }
    store_pointer;

    // convert to type
    void integer_to_double();
    void integer_to_string();
    void double_to_integer();
    void double_to_string();
    void string_to_integer();
    void string_to_double();

public:

...

    int & ToInt()
    {
        switch (store_type)
        {
            case Integer: break;
            case Double: double_to_integer(); break;
            case String: string_to_integer(); break;
        }
        return * as_integer;
    }

...

}
like image 7
Evan Dark Avatar answered Nov 12 '22 05:11

Evan Dark


I have implemeted a simple variant class myself (without using third-party libs). Each of the ToXxx functions contains a switch over m_type (enum that indicates the type currently being held). For string conversion (both from and to) I use std::stringstream. It's trivial, really. Pretty much like Mooing Duck suggested.

P. S. If frequent calling of string conversion for the same value is intended, I'd cache it.

like image 2
Violet Giraffe Avatar answered Nov 12 '22 05:11

Violet Giraffe