user513064
user513064

Reputation:

Pass any data type to function in C++

For the purposes of one of my projects I would like to make a typedef struct that can contain any type of data. I made one that stores in an enum what kind of data it is holding, as well as a void * that actually holds the data.

In my current attempts it worked quite well with ints, but when I tried to add other types like std::string or float it didn't work too well.

I've tried a few different methods of trying to put the data into the container and get it out, but none so far have worked for everything.

What is the best way to do this? I would like to stuff any type of data into a structure and be able to retrieve the data in the same form.

Edit:

typedef struct{
    DataTypeInt,
    DataTypeFloat,
    DataTypeString
}DataType;

typedef struct{
    DataType type;
    void *data;
}Data;

At first I was testing only with ints and I would simply use (void *)foo to input the data. Then put (int)bar.data to get the data out. This worked marvellously with int types but i received compiler errors if I tried std::string or float

After fumbling around with different pointer and casting configurations, and searching the internet, I ended up putting simply foo.data = &bar to input the data and float foo = *(float*)bar.data to get the data out. This didn't give any errors, but the data didn't come out correctly. Any int or float that came through the other side would be 0 and strings would be random symbols.

I also tried both of those methods with a wrapper struct to contain each data type and then pass that as data.

Upvotes: 0

Views: 4054

Answers (4)

mrjoltcola
mrjoltcola

Reputation: 20842

1) If you need a variant, and don't mind using Boost, consider Boost.Variant or Boost.Any. It is clean, robust and mature.

2) Use overloading to achieve a non-union variant. (The problem with true unions is for non-POD types, its ugly enough that even seasoned developers forget how to do it right). Best to stay away.

class Variant 
{
    int i;
    float j;
    std::string s;

    // Add all of your typed getter and setters
}

With overloading you create a non-union wrapper class with a member for each type, then add getter/setter methods with overrides for each type. As long as you aren't planning to create millions of this type, it is safer than using void *

3) Use sub-type polymorphism - A base class that specifies all of the different possible types, then extended with specific implementations.

class VariantBase
{
public:
    virtual int          get_int()    { throw new "Not implemented"; }
    virtual float        get_float()  { throw new "Not implemented"; }
    virtual std::string  get_string() { throw new "Not implemented"; }
}

class VariantInt
{
    int val;
public:
    virtual int          get_int()      { return val; }
    virtual void         set_int(int i) { val = i; }
}

class VariantString
{
   ...
}

4) If you are used to C#, where all types derive from System.Object, the closest thing to that in C++ is void *, however, unlike C#, what you stash in a void * loses its type temporarily, and you can't use reflection. More importantly, if you stomp on it, the Garbage Collector won't save you. So instead of just hoping for the best, you should implement a safe Variant wrapper around the void * to protect you from yourself.

You can put an object into void *

void * vp = obj;

But to get it out you need to use a C++ cast.

MyClass * obj = static_cast<MyClass*>(vp);

You can implement a safe Variant using void * within the rules of C++. Remember, there is Boost.Variant (1). I put this here just to demonstrate. It is DANGEROUS and generally means you passed over other valid solutions. However, C programmers use the technique often. The C++ language creator, Bjarne Stroustrup explicitly considers this legal and valuable, I consider it simply part of C++. The general idea here is a simple 3 type variant. Better C++ developers would do much of this with templates, but since you are new to C++, its a non-template solution I hope you can understand. When using void * you must always be sure what type the pointer is referring to before you try to convert it, so a good rule is to wrap all of the accessors in a function that validates the enum value. Check your enum value first, and throw an exception if mismatched (someone called get_int on a string) though some variants support conversions. Since you are dealing with pointers, the set_val_type() method will delete the old memory first. Note the use of static_cast<>, it is the only way you should ever convert a void * in C++ if you want clean, defined code.

enum VariantValType { None, Int, Float, StdString };

class Variant
{
     VariantValType valType;
     void *val;
public:
     Variant() : val(nullptr), valType(None) {}
     ~Variant() { reset_val(); }

     void set_val_type(VariantValType t) {
          if (t == valType)
               return;
          reset_val();
          valType = t;
     }

     void reset_val() {
          switch (valType) {
          case None:          break;
          case Int:           delete (static_cast<int*>(val)); break;
          case Float:         delete (static_cast<float*>(val)); break;
          case StdString:     delete (static_cast<std::string*>(val)); break;
          default:            throw "Unknown value type"; break;
          }
          valType = None;
          val = nullptr;
     }

     int get_int() {
          if (valType != Int)
               throw "Variant type mismatch";
          return *(static_cast<int*>(val));
     }

     void set_int(int i) {
          set_val_type(Int);
          val = new int{ i };
     }

     float get_float() {
          if (valType != Float)
               throw "Variant type mismatch";
          return *(static_cast<float*>(val));
     }

     void set_float(float f) {
          set_val_type(Float);
          val = new float{ f };
     }

     std::string& get_string() {
          // If you like, implement conversion here with std::to_string() like so
          // if(valType == Int) return std::to_string(*(static_cast<int*>(val)));

          if (valType != StdString)
               throw "Variant type mismatch";
          return *(static_cast<std::string*>(val));
     }

     void set_string(const std::string& s) {
          set_val_type(StdString);
          val = new std::string(s);
     }
};

Upvotes: 1

MNS
MNS

Reputation: 1394

Here is a complete demonstration of defining a generic data type and using it in code. I wrote this demonstration some time ago. Now it is updated to support your requirement of holding a std::string datatype. Support for float is present in the form of 'double' datatype.

You can add any data type to it. Care should be taken when assigning datatype. It means, you know what you are doing.

Copy paste below code to a console application and see how it works.

#include "windows.h"
#include "vector"
#include "string"

using namespace std;

enum GENERIC_DATA_TYPE
{
   GENERIC_TYPE_NONE = 0,
   GENERIC_TYPE_INT,
   GENERIC_TYPE_SHORT,
   GENERIC_TYPE_DOUBLE,
   GENERIC_TYPE_CHAR,
   GENERIC_TYPE_STD_STRING,
   GENERIC_TYPE_STD_WSTRING,
};

#define SAFE_ARRAY_DELETE( ptrArray ) \
delete[] ptrArray;\
ptrArray = 0;

#define SAFE_DELETE( ptrPointer ) \
delete ptrPointer;\
ptrPointer = 0;

struct GENERIC_DATA
{
   GENERIC_DATA_TYPE m_DataType;
   UINT              m_DataSize;
   void*             m_ptrData;

   GENERIC_DATA() : m_DataType( GENERIC_TYPE_NONE ),
                    m_DataSize( 0 ),
                    m_ptrData( 0 )
   {
   }

   ~GENERIC_DATA()
   {
       CleanMemory();
   }

   bool CleanMemory()
   {
       try
       {
           switch( m_DataType )
           {
               case GENERIC_TYPE_INT:
               {
                   int* ptrInt = reinterpret_cast<int*>( m_ptrData );
                   SAFE_ARRAY_DELETE( ptrInt );
                   break;
               }
               case GENERIC_TYPE_SHORT:
               {
                   short* ptrShort = reinterpret_cast<short*>( m_ptrData );
                   SAFE_ARRAY_DELETE( ptrShort );
                   break;
               }
               case GENERIC_TYPE_DOUBLE:
               {
                   double* ptrDouble = reinterpret_cast<double*>( m_ptrData );
                   SAFE_ARRAY_DELETE( ptrDouble );
                   break;
               }
               case GENERIC_TYPE_CHAR:
               {
                   // Since string is kept as an array of string,
                   // we need to iterate each string
                   // and delete.
                   char** ptrString = reinterpret_cast<char**>( m_ptrData );
                   for( UINT uCounter = 0; m_DataSize > uCounter; ++uCounter )
                   {
                        SAFE_ARRAY_DELETE( ptrString[uCounter]);
                   }

                   // Now delete the double pointer.
                   SAFE_ARRAY_DELETE( ptrString );
                   break;
               }
               case GENERIC_TYPE_STD_STRING:
                {
                    string* pstdString = reinterpret_cast<string*>( m_ptrData );
                    SAFE_DELETE ( pstdString );
                    break;
                }
               case GENERIC_TYPE_STD_WSTRING:
                {
                    wstring* pstdString = reinterpret_cast<wstring*>( m_ptrData );
                    SAFE_DELETE ( pstdString );
                    break;
                }
           }
           m_DataSize = 0;
           m_DataType = GENERIC_TYPE_NONE;
           return true;
       }
       catch( ... )
       {
       }
       return false;
   }
}; 

typedef vector<GENERIC_DATA*> GENERIC_DATA_VECTOR;

int main()
{
    GENERIC_DATA_VECTOR vData;

    // Allocate memory to hold the data
    GENERIC_DATA* ptrData = new GENERIC_DATA();

    // PUT SOME INTERGERS
    // Of course the array size would be determined at runtime.
    const int INT_COUNT = 10;
    int* ptrIntArray = new int[INT_COUNT]; 

    UINT nCounter = 0;

    // Fill ptrIntArray with some integers
    for( nCounter = 0; INT_COUNT > nCounter; ++nCounter )
    {
        ptrIntArray[nCounter] = rand();
    }
    // It is verly important to set the correct type here.
    ptrData->m_DataType = GENERIC_TYPE_INT;
    ptrData->m_DataSize = INT_COUNT;
    ptrData->m_ptrData  = ptrIntArray;

    // Now put the data in the vector;
    vData.push_back( ptrData );


    // PUT A STRING
    string* pstrString = new string();
    *pstrString = "Helo World";

    ptrData = new GENERIC_DATA();
    // It is verly important to set the correct type here.
    ptrData->m_DataType = GENERIC_TYPE_STD_STRING;
    ptrData->m_DataSize = 1;
    ptrData->m_ptrData  = pstrString;

    // Now put the data in the vector;
    vData.push_back( ptrData );



    // Now, at a later time we can manipulate each
    // or the required type in the vector as below.
    GENERIC_DATA_VECTOR::iterator DATA_VECTOR_END = vData.end();
    GENERIC_DATA_VECTOR::iterator itrData = vData.begin();
    GENERIC_DATA* ptrDataToProcess = 0;
    for( ; itrData != DATA_VECTOR_END; ++itrData )
    {
        ptrDataToProcess = ( *itrData );

        // Look for integer
        if( GENERIC_TYPE_INT == ptrDataToProcess->m_DataType )
        {
            int* ptrIntArray = reinterpret_cast<int*>( ptrDataToProcess->m_ptrData );

            // Process integers
            for( nCounter = 0; ptrDataToProcess->m_DataSize > nCounter; ++nCounter )
            {
                printf( "\n %d", ptrIntArray[nCounter]);
            }
            continue; // Continue to next data.
        }

        // Look for string
        if( GENERIC_TYPE_STD_STRING == ptrDataToProcess->m_DataType )
        {
            string* pstrString = reinterpret_cast<string*>( ptrDataToProcess->m_ptrData );

            // Process the string
            printf( "\n %s", pstrString->c_str());
        }
        continue; // Continue to next data.
    }

    // Once we finish with the data, iterate the vector and delete each entry.
    DATA_VECTOR_END = vData.end();
    itrData = vData.begin();
    for( ; itrData != DATA_VECTOR_END; ++itrData )
    {
        delete ( *itrData );
    }

    return 0;
}

Upvotes: 0

lappet
lappet

Reputation: 114

you can overload that function.. here the simple example..

int AddI(int nX, int nY)
{
return nX + nY;
}

double AddD(double dX, double dY)
{
return dX + dY;
}

Upvotes: 0

djechlin
djechlin

Reputation: 60768

You pass an arbitary pointer to your data and manipulate that. See void* type.

Upvotes: 0

Related Questions