• Advertisement
Sign in to follow this  

Perl style scalar type in C++?

This topic is 4730 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

in my game, various components need to signal each other by using a signal method
   void Signal(const string &id , Signal sig);

now the signal should be able to hold various different data types, in a stack (or a hash, would be equally as good).
    int
    int
    char
    string
I've only come up with 2 solutions. 1 is using ints and casting everything to ints (anything larger, is cast as a pointer - which is deleted afterwards !), or allocating memory using new and sizeof (again pointers, and void data type). What are you thoughts/suggestions/warnings? [Edited by - Genjix on March 6, 2005 5:37:16 PM]

Share this post


Link to post
Share on other sites
Advertisement
Why not just have different types of signals subclass Signal? better yet, why not have different per-signal methods? Best of all, why not use a premade signal/slot framework such as that provided by Boost?

Share this post


Link to post
Share on other sites
I won't go into the specific details, but having different subclasses for each signal wouldn't be appropriate.

Signal sig;
sig.Int(10);
sig.Str("Hello");
sig.Ptr((void*)&obj);

What would you do when a certain function needs to parse the structure?

void A::Foo(Signal s)
{
//... if i want to interpret
// values in s, how do i this?
// I still, can only access members
// that Signal has.
}

Eventhough boost's libraries are good, its signal/slot implementation doesn't suit what I'm doing. I do appreciate your help though.

[Edited by - Genjix on March 3, 2005 3:20:20 PM]

Share this post


Link to post
Share on other sites
Off the top of my head something this simple should do the job. Ofcourse it won't work for types you don't want to copy & you'll have to grab the thing by reference in your function (& not call the function the same name as the class :P), but you should really write a copy constructor for it anyway to handle such things.
struct Signal(){
/**/ Signal(char &value)
value(&new char(value)),
type(type_char)
{}
/**/ Signal(int &value)
value(&new int(value)),
type(type_int)
{}
/**/ Signal(string &value)
value(&new string(value)),
type(type_string)
{}
/**/ ~Signal(){delete value;}

void* value;
enum{
type_int,
type_char,
type_string
}type;
};

Share this post


Link to post
Share on other sites
Quote:
Original post by Genjix
//... if i want to interpret
// values in s, how do i this?
// I still, can only access members
// that Signal has.
By using RTTI. Of course, this can be ugly (just as variant types like you want can be ugly), which is why a premade signal/slot library is useful.

In what way is the boost signal/slot library insufficient for your needs?

Share this post


Link to post
Share on other sites

#ifndef NODE_H
#define NODE_H

#include "signal.h"

class World;

/**Base abstract 'Node' framework class

This class is the class inherited by each node.
Node's are different part's of the application,
and can be thought of as little clients.
Signals are recieved and sent as strings.*/

class Node
{
public:
/**update class pointer to parent*/
Node(World *par);
/***/
~Node();

/**Recieve and Interpret a signal

RecieveSignal should be overriden, by all derived
node classes*/

virtual void RecieveSignal(const string &id , Signal &sig);
protected:
/**Send a signal to parent to broadcast

Broadcast should not be overriden as it is used
to communicate with the other nodes, via the parent*/

void Broadcast(const string &id , Signal &sig);
/**Send a signal to parent to relay

SendSignal should not be overriden as it is used
to communicate with the other nodes, via the parent*/

void SendSignal(const string &name , const string &id , Signal &sig);
private:
World *parent; ///< Node's Parent
};

#endif



All the different nodes, are created, and registered with the app. Different nodes need to communicate, and parameters need to be passed around.

As for a new data type for each different type of signal... well the reciever and sender BOTH need to know the signal type (so reciever can cast back to type to read data), and since there are potentially lots of signals, and a broadcast...

ProPuke's solution is the simplest, and my favorite... but what happens with lots of data types, use pointers? I might as well cast everything to ints, and anything else read the pointer and cast as an int.

boost::any is exactly what i need (especially type checking)... but i need to link against boost, and its a bit OTT for whats needed. What would be better :D lmao, would be if anyone could tell me how its is able to accept any type, and how it internally stores the typeinfo.

If it helps, the system im (trying) to design is more like Qt's mechanism.

Thank you for your time.

Share this post


Link to post
Share on other sites
this is what I've decided to go for (I still want your ideas :) )

main.cpp


#include "world/world.h"
#include "node/testnode.h"
#include "event/eventinterpreter.h"

int main()
{
World myworld;

TestNode genericnode(&myworld);

myworld.Register("GenericNode",genericnode);

/*testing testing*/
Signal soh("this is message");
soh.params["age"] = 10;
soh.params["name"] = (int)"Amir Taaki";
myworld.Broadcast("hello",soh);
myworld.SendSignal("GenericNode","bye",soh);

/*tie our World to eventinterpreter*/
EventInterpreter ei(myworld,"amir.lua");

return 0;
}


signal.h

#ifndef SIGNAL_H
#define SIGNAL_H

#include "types/stlext.h"
#include <string>
using namespace std;

/**not yet implemented*/
class Signal
{
public:
/***/
Signal(const string &d);
/***/
~Signal();

map<string , int> params;///< signal parameters (cast to ints)
const string debug; ///< debug message
};

#endif


and I unroll the signal

cout << (const char*)sig.params["name"] << " is "
<< sig.params["age"] << "\n";

normally I wouldn't ever dream of doing something as simple as this :P

Share this post


Link to post
Share on other sites
You can store type info using polymorphism.

class BType
{
virtual ~BType() {}
};

template<typename T> class DType
{
public:
virtual ~DType() {}
T the_object;
/* appropriate constructors... */
};


In your 'any' class, you have a templated set function that creates an object of type DType<T>. You store this using a pointer to BType. Later, if you want to check to see if it's of type A, you attempt a dynamic_cast<DType<A>*>(ptr_to_BType). If it returns non-null, you've got a value object of DType<A> and can access the_object. If it returns null, it's not the right type.

I've got a non-boost variant class that operates this way. Let me slice it out of my lib and I'll pass it along. :D

Share this post


Link to post
Share on other sites

#ifndef JM_ANY_H
#define JM_ANY_H

#include <algorithm>
#include <typeinfo>

class Any
{
private:
class Exception: public std::exception
{
public:
virtual const char * what() const throw() { return "Any Exception"; }
virtual ~Exception() throw() {}
};

class EmptyException: public Exception
{
virtual const char * what() const throw()
{ return "Attempted extraction from empty Any"; }
virtual ~EmptyException() throw() {}
};

class WrongTypeException: public Exception
{
virtual const char * what() const throw()
{ return "Reguested wrong type from Any"; }
virtual ~WrongTypeException() throw() {}
};

template <typename T> class NoConst { public: typedef T type; };
template <typename T> class NoConst<const T> { public: typedef T type; };

class TypeData
{
public:
virtual ~TypeData() {}
virtual TypeData* clone() = 0;
virtual std::string TypeName() = 0;
}; //End class TypeData

template <typename ValueType>
class T_TypeData: public TypeData
{
public:
ValueType value;
T_TypeData(const ValueType& val) : value(val) {}
virtual ~T_TypeData() {}
virtual TypeData* clone() { return new T_TypeData(value); }
virtual std::string TypeName() { return typeid(ValueType).name(); }
}; //End class T_TypeData<ValueType>

TypeData* value;

void swap(Any& with) { std::swap(value,with.value); }

public:
~Any() { delete value; }
Any() : value(0) {}
Any(const Any& rhs) : value( rhs.value != 0 ? rhs.value->clone() : 0) {}
void operator=(const Any& rhs) { swap(Any(rhs)); }
bool empty() { return (value == 0); }

template<typename ValueType> void extract(ValueType& into) const
{
if (!value) throw EmptyException();
T_TypeData<ValueType>* val =
dynamic_cast<T_TypeData<NoConst<ValueType>::type>*>(value);
if (val != 0) into = val->value;
else throw WrongTypeException();
}

template<typename ValueType> void insert(const ValueType& in)
{
delete value;
value = new T_TypeData<NoConst<ValueType>::type>(in);
}

template<typename ValueType>
bool isOfType(const ValueType&) const
{
if (!value) return false;
return (0 != dynamic_cast<T_TypeData<NoConst<ValueType>::type>*>(value));
}

template<typename ValueType> bool extractCheck(ValueType& into) const
{
try
{
extract(into);
return true;
}
catch (Exception&) return false;
}
}; //End class Any

#endif



Some notes
: An Any owns what it holds. Passing it by value means the contents are deep-copied.
: It should work on MSVC6.0, however, the no-const feature won't work. That is, it will consider const int and int to be different types, not the same type.
: It throws exceptions. Use extractCheck for no-throw behavior. (It will still propogate exceptions thrown by the contained type)

Share this post


Link to post
Share on other sites
Quote:
Original post by Deyja
*** Source Snippet Removed ***

Some notes
: An Any owns what it holds. Passing it by value means the contents are deep-copied.
: It should work on MSVC6.0, however, the no-const feature won't work. That is, it will consider const int and int to be different types, not the same type.
: It throws exceptions. Use extractCheck for no-throw behavior. (It will still propogate exceptions thrown by the contained type)

Wow, your class is very nice. I take it all the types, inherit Any?

Share this post


Link to post
Share on other sites
Quote:
Original post by Genjix
Wow, your class is very nice. I take it all the types, inherit Any?


No. He uses template member functions. Look at his insert and extract. Look also at boost::any for a 'standard' solution.

Share this post


Link to post
Share on other sites
Quote:
Original post by Fruny
Quote:
Original post by Genjix
Wow, your class is very nice. I take it all the types, inherit Any?


No. He uses template member functions. Look at his insert and extract. Look also at boost::any for a 'standard' solution.

i saw your above post, and have been looking into boost::any - boost surprises me once again. It is a great library.


As for the code, I've just got round to compiling it. seems g++, does not like type naming via template member functions.

Share this post


Link to post
Share on other sites
Quote:
type naming via template member functions


Um. What?

Share this post


Link to post
Share on other sites
as in, i explicitly have to define the type when using the templated member functions. as for g++ warnings here, they are.

genjix@linux:~/media/tmp> g++ any.cpp
any.cpp: In member function `void Any::extract(ValueType&) const':
any.cpp:73: warning: `Any::NoConst<T>::type' is implicitly a typename
any.cpp:73: warning: implicit typename is deprecated, please see the
documentation for details
any.cpp: In member function `void Any::insert(const ValueType&)':
any.cpp:81: warning: `Any::NoConst<T>::type' is implicitly a typename
any.cpp:81: warning: implicit typename is deprecated, please see the
documentation for details
any.cpp: In member function `bool Any::isOfType(const ValueType&) const':
any.cpp:88: warning: `Any::NoConst<T>::type' is implicitly a typename
any.cpp:88: warning: implicit typename is deprecated, please see the
documentation for details
any.cpp: In member function `std::string Any::T_TypeData<ValueType>::TypeName()
[with ValueType = int]':
any.cpp:81: instantiated from here
any.cpp:54: error: return type `struct std::basic_string<char,
std::char_traits<char>, std::allocator<char> >' is incomplete
any.cpp:54: error: conversion from `const char*' to non-scalar type `
std::basic_string<char, std::char_traits<char>, std::allocator<char> >'
requested

i can solve errors, but not warnings.

thanks all, for your help.

Share this post


Link to post
Share on other sites
It seems to be missing some typename keywords. e.g. where the code refers to Any::NoConst<T>::type, it should really be typename Any::NoConst<T>::type.

Share this post


Link to post
Share on other sites
It only refers to no_const<T>::type in the context of invoking another template. You wouldn't invoke a template as 'sometemplate<typename char>', would you?


template <typename T> class NoConst { public: typedef T type; };
template <typename T> class NoConst<const T> { public: typedef T type; };
T_TypeData<NoConst<ValueType>::type>(in);


Fruny; please do be explaining in more detail why G++ is complaining about this, and more importantly, why MSVC8.0 is not.

[edit]
Do I see 'any.cpp'? This is 'any.h'. Have you seperated it into two files?
[/edit]

Share this post


Link to post
Share on other sites
here is the exact code i used. (so you can copy and paste)

huge code listing

the strangest thing (for me), is that swap error.

as for using headers... well i don't really use headers when testing things out quickly. If it makes a difference im using linux gnu libstdc++ (shouldn't do, should it?).

[Edited by - Genjix on March 8, 2005 9:02:09 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Deyja
Fruny; please do be explaining in more detail why G++ is complaining about this, and more importantly, why MSVC8.0 is not.


When you write Any::NoConst<T>::type, the compiler cannot guess whether type is a type or a static variable: it could vary depending on T. The default behavior is to assume it is a member variable, and to only treat it as a type if you write typename Any::NoConst<T>::type.

It is possible that the compiler guesses in some circumstances (where only a type would work), but that's not standard behavior, hence the warning from g++: it used to do it, but the functionality has been removed for standard compliance (in GCC 3.4, you get an error).

I assume VC++ 8.0 does not issue a warning either because your warning level is too low, or simply for reasons of backward-compatibility. VC accepts certain illegal code because doing otherwise would break existing Windows applications (if not Windows itself [pig]). Try disabling language extensions (/Za flag) and see what happens. Note that doing so may prevent you from using even the <windows.h> header - which does rely on language extensions ... see what I meant by breaking Windows apps [grin]?

Share this post


Link to post
Share on other sites
Quote:
Original post by Fruny
Quote:
Original post by Deyja
Fruny; please do be explaining in more detail why G++ is complaining about this, and more importantly, why MSVC8.0 is not.

I assume VC++ 8.0 does not issue a warning either because your warning level is too low, or simply for reasons of backward-compatibility. VC accepts certain illegal code because doing otherwise would break existing Windows applications (if not Windows itself [pig]). Try disabling language extensions (/Za flag) and see what happens. Note that doing so may prevent you from using even the <windows.h> header - which does rely on language extensions ... see what I meant by breaking Windows apps [grin]?

lol, i see. Also change your = to this.

void operator=(Any& rhs) { swap(rhs); }

now it compiles fine until i uncomment a.insert(x)
genjix@linux:~/media/tmp> g++ any.cpp
any.cpp: In member function `std::string Any::T_TypeData<ValueType>::TypeName()
[with ValueType = int]':
any.cpp:80: instantiated from here
any.cpp:54: error: return type `struct std::basic_string<char,
std::char_traits<char>, std::allocator<char> >' is incomplete
any.cpp:54: error: conversion from `const char*' to non-scalar type `
std::basic_string<char, std::char_traits<char>, std::allocator<char> >'
requested

as far as I can tell,

line 54: virtual std::string TypeName() { return typeid(ValueType).name(); }
line 80: value = new T_TypeData<typename NoConst<ValueType>::type>(in);

if I create a temporary string object and return that, it claims the storage size of the tmp object is unknown. hmmm :/

#ifndef JM_ANY_H
#define JM_ANY_H

#include <algorithm>
#include <typeinfo>

class Any
{
private:
class Exception: public std::exception
{
public:
virtual const char * what() const throw() { return "Any Exception"; }
virtual ~Exception() throw() {}
};

class EmptyException: public Exception
{
public:
EmptyException(){}
virtual const char * what() const throw()
{ return "Attempted extraction from empty Any"; }
virtual ~EmptyException() throw() {}
};

class WrongTypeException: public Exception
{
public:
WrongTypeException(){}
virtual const char * what() const throw()
{ return "Reguested wrong type from Any"; }
virtual ~WrongTypeException() throw() {}
};

template <typename T> class NoConst { public: typedef T type; };
template <typename T> class NoConst<const T> { public: typedef T type; };

class TypeData
{
public:
virtual ~TypeData() {}
virtual TypeData* clone() = 0;
virtual std::string TypeName() = 0;
}; //End class TypeData

template <typename ValueType>
class T_TypeData: public TypeData
{
public:
ValueType value;
T_TypeData(const ValueType& val) : value(val) {}
virtual ~T_TypeData() {}
virtual TypeData* clone() { return new T_TypeData(value); }
virtual std::string TypeName() { return typeid(ValueType).name(); }
}; //End class T_TypeData<ValueType>

TypeData* value;

void swap(Any &with) { std::swap(value , with.value); }

public:
~Any() { delete value; }
Any() : value(0) {}
Any(const Any& rhs) : value( rhs.value != 0 ? rhs.value->clone() : 0) {}
void operator=(Any& rhs) { swap(rhs); }
bool empty() { return (value == 0); }

template<typename ValueType> void extract(ValueType& into) const
{
if (!value) throw EmptyException();
T_TypeData<ValueType>* val =
dynamic_cast<T_TypeData<typename NoConst<ValueType>::type>* >(value);
if (val != 0) into = val->value;
else throw WrongTypeException();
}

template<typename ValueType> void insert(const ValueType& in)
{
delete value;
value = new T_TypeData<typename NoConst<ValueType>::type>(in);
}

template<typename ValueType>
bool isOfType(const ValueType&) const
{
if (!value) return false;
return (0 != dynamic_cast<T_TypeData<typename NoConst<ValueType>::type>*>(value));
}

template<typename ValueType> bool extractCheck(ValueType& into) const
{
try
{
extract(into);
return true;
}
catch (Exception&) {return false;}
}
}; //End class Any

#endif

int main()
{
int x = 67;
Any a;
//a.insert(x);
}


Share this post


Link to post
Share on other sites
Quote:
lol, i see. Also change your = to this.
void operator=(Any& rhs) { swap(rhs); }


Absolutly not. You have just changed the behavior of operator=. It is properly implemented as I had it before - void operator=(const Any& rhs) { swap(Any(rhs)); }
Your version properly changes the assigned-to object, but destroys the assigned-from object. Also, it is not const correct. If G++ complains about passing a psuedo-const temporary object into swap, reimplement the function body as two lines, Any t(rhs); swap(t);
The only other acceptable change is to return *this. I'm sure I had a good reason for leaving that out in the first place, but I've forgotten it.

As for your other errors - add '#include <string>'. I removed some library stuff from the header before I posted it and never tested it in isolation. Sorry.
Alternativly, you can just remove the TypeName method from the typedata classes. It's not actually used anywhere...

Share this post


Link to post
Share on other sites
didn't notice missing string header. Thanks for showing me how multi type variables work. amazing. as for operator=, shouldn't it be ok, as long as you don't pass by const reference, or is that why you create a temporary copy?

Share this post


Link to post
Share on other sites
If you removed the & from your version, it would function the same - except you couldn't assign const objects to non-const ones.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement