I am in need to (or better, I have the chance to) refactor some code in order to make it cleaner.
I would like to use some template, as I think this is a good candidate, in order to reduce the code duplication.
Here is my hpp
class Monetary
{
public:
Monetary();
Monetary(const rapidjson::Value& iMonetary);
virtual ~Monetary();
[...cut...]
private:
static void initMember(const rapidjson::Value& iMonetary, const char* iName, int& oMember);
static void initMember(const rapidjson::Value& iMonetary, const char* iName, std::string& oMember);
private:
int _amount;
int _decimal_place;
std::string _currency;
std::string _type;
};
And here is the implementation for the the initMember methods:
static void Monetary::initMember(const rapidjson::Value& iMonetary, const char* iName, int& oMember)
{
rapidjson::Value::ConstMemberIterator aIterator;
aIterator = iMonetary.FindMember(iName);
if ( aIterator != iMonetary.MemberEnd() &&
aIterator->value.IsNumber() )
{
oMember = iMonetary[iName].GetInt();
}
}
static void Monetary::initMember(const rapidjson::Value& iMonetary, const char* iName, std::string& oMember)
{
rapidjson::Value::ConstMemberIterator aIterator;
aIterator = iMonetary.FindMember(iName);
if ( aIterator != iMonetary.MemberEnd() &&
aIterator->value.IsNumber() )
{
oMember = iMonetary[iName].GetString();
}
}
I was thinking about writing something like
template<typename T>
void Monetary::initMember(const rapidjson::Value& iMonetary, const char* iName, T& oMember)
{
rapidjson::Value::ConstMemberIterator aIterator;
aIterator = iMonetary.FindMember(iName);
if (aIterator == iMonetary.MemberEnd())
{
return;
//throw monetaryException
}
assignFromValue(iMonetary[iName], oMember);
}
template<>
void Monetary::assignFromValue<int>(const rapidjson::Value& iValue, int& oMember)
{
if (!iValue.IsNumber())
{
return;
//throw monetaryException
}
oMember = iValue.GetInt();
}
template<>
void Monetary::assignFromValue<std::string>(const rapidjson::Value& iValue, std::string& oMember)
{
if (!iValue.IsString())
{
return;
//throw monetaryException
}
oMember = iValue.GetString();
}
Is there any clewer way to do that ?
My suggestions:
You don't need to make assignFromValue member functions. If you can implement functionality using non-member functions, you should prefer non-member functions. See How Non-Member Functions Improve Encapsulation and How Non-Member Functions Improve Encapsulation.
You don't need to make assignFromValue function templates. They can be simple overloads.
void assignFromValue(const rapidjson::Value& iValue,
int& oMember)
{
if (!iValue.IsNumber())
{
return;
//throw monetaryException
}
oMember = iValue.GetInt();
}
void assignFromValue(const rapidjson::Value& iValue,
std::string& oMember)
{
if (!iValue.IsString())
{
return;
//throw monetaryException
}
oMember = iValue.GetString();
}
I think I'd do it with a tag-dispatched converter object:
#include <string>
#include <type_traits>
#include <stdexcept>
// simulate your value class
struct Value
{
bool IsNumber() const;
bool IsString() const;
std::string getString() const;
int getInt() const;
};
// a tag type for easy tag dispatching
template<class Type> struct tag {};
// a converter object contains all rules and conversion sequences
struct ValueConverter
{
std::string operator()(tag<std::string>, const Value& v) const
{
if (not v.IsString()) throw std::invalid_argument("not a string");
return v.getString();
}
int operator()(tag<int>, const Value& v) const
{
if (not v.IsNumber()) throw std::invalid_argument("not a number");
return v.getInt();
}
};
// write the member once
template<class Target>
void initMember(const Value& iMonetary, const char* iName, Target& oMember)
{
using target_type = std::decay_t<Target>;
auto converter = ValueConverter();
oMember = converter(tag<target_type>(), iMonetary);
}
Related
I created sets of functions using the type-erasure design pattern:
Encodable: encode(), decode()
Printable: print()
If I overload these functions with MyStruct1 and MyStruct2, I'll be able to wrap these types in the type-erasure wrappers Encodable and Printable and indirectly call those functions using the wrappers functions and store MyStruct1 and MyStruct2 in a heterogeneous container like std::vector<Encodable>.
My problem arised when I wanted to use these objects both as an Encodable and a Printable or when I wanted to convert one to the other.
#include <cstdint>
#include <utility>
#include <memory>
#include <vector>
class Encodable {
private:
struct EncodableConcept {
virtual ~EncodableConcept() = default;
virtual std::vector<uint8_t> _encode() const = 0;
virtual bool _decode(std::vector<uint8_t> const& byteVector) = 0;
virtual std::unique_ptr<EncodableConcept> clone() const = 0;
};
template<typename EncodableT>
struct EncodableModel : public EncodableConcept {
EncodableModel(EncodableT inst) : _inst{std::move(inst)}
{}
std::vector<uint8_t> _encode() const override {
return encode(_inst);
}
bool _decode(std::vector<uint8_t> const& byteVector) override {
return decode(_inst, byteVector);
}
std::unique_ptr<EncodableConcept> clone() const override {
return std::make_unique<EncodableModel>(*this);
}
EncodableT _inst;
};
friend std::vector<uint8_t> encode(Encodable const& inst) {
return inst.pimpl->_encode();
}
friend bool decode(Encodable & inst, std::vector<uint8_t> const& byteVector) {
return inst.pimpl->_decode(byteVector);
}
public:
template<typename EncodableT>
Encodable(EncodableT inst)
: pimpl{std::make_unique<EncodableModel<EncodableT>>(std::move(inst))}
{}
Encodable(Encodable const& other)
: pimpl(other.pimpl->clone())
{}
Encodable& operator=(Encodable const& other) {
// Copy-and-swap idiom
Encodable tmp(other);
std::swap(pimpl, tmp.pimpl);
return *this;
}
// Move is not implemented to prevent the pimpl to be nullptr after move operation.
// Upon move the copy constructor or the copy assignment operator is going to be called.
private:
std::unique_ptr<EncodableConcept> pimpl;
};
class Printable {
private:
struct PrintableConcept {
virtual ~PrintableConcept() = default;
virtual void _print() const = 0;
virtual std::unique_ptr<PrintableConcept> clone() const = 0;
};
template<typename PrintableT>
struct PrintableModel : public PrintableConcept {
PrintableModel(PrintableT inst) : _inst{std::move(inst)}
{}
void _print() const override {
print(_inst);
}
std::unique_ptr<PrintableConcept> clone() const override {
return std::make_unique<PrintableModel>(*this);
}
PrintableT _inst;
};
friend void print(Printable const& inst) {
inst.pimpl->_print();
}
public:
template<typename PrintableT>
Printable(PrintableT inst)
: pimpl{std::make_unique<PrintableModel<PrintableT>>(std::move(inst))}
{}
Printable(Printable const& other)
: pimpl(other.pimpl->clone())
{}
Printable& operator=(Printable const& other) {
// Copy-and-swap idiom
Printable tmp(other);
std::swap(pimpl, tmp.pimpl);
return *this;
}
// Move is not implemented to prevent the pimpl to be nullptr after move operation.
// Upon move the copy constructor or the copy assignment operator is going to be called.
private:
std::unique_ptr<PrintableConcept> pimpl;
};
struct MyStruct1 {
MyStruct1(int x) : x{x} {}
int x;
};
std::vector<uint8_t> encode(MyStruct1 const& inst) {
std::vector<uint8_t> byteVector;
// ...
return byteVector;
}
bool decode(MyStruct1 & inst, std::vector<uint8_t> const& byteVector) {
// ...
return true; // Success
}
void print(MyStruct1 const& inst) {
printf("MyStruct1{%d}\n", inst.x);
}
struct MyStruct2 {
MyStruct2(float y) : y{y} {}
float y;
};
std::vector<uint8_t> encode(MyStruct2 const& inst) {
std::vector<uint8_t> byteVector;
// ...
return byteVector;
}
bool decode(MyStruct2 & inst, std::vector<uint8_t> const& byteVector) {
// ...
return true; // Success
}
void print(MyStruct2 const& inst) {
printf("MyStruct2{%f}\n", inst.y);
}
std::vector<Encodable> readFromSomewhere() {
std::vector<Encodable> readEncodables;
// Read from file, socket, etc...
readEncodables = {MyStruct1{1}, MyStruct2{2.2}, MyStruct1{3}, MyStruct2{4.4}};
return readEncodables;
}
int main(int argc, char** argv) {
std::vector<Encodable> readEncodables = readFromSomewhere();
// TODO: How can I print readEncodables using the overloaded print functions?
// For example:
for (Encodable const& obj : readEncodables) {
// print(obj); // Compilation error (thanks god)
}
// TODO: How can I convert an Encodable to a Printable object?
std::vector<Printable> convertedPrintables;
for (Encodable const& obj : readEncodables) {
// convertedPrintables.emplace_back(obj); // Compilation error (thanks god)
// print(*convertedPrintables.crbegin());
}
return 0;
}
How can I easily convert an Encodable to a Printable and vice versa?
Can I somehow directly use an Encodable as a Printable if the print() function is overloaded for the given type?
My only (bad) solution is to create a templated get() function in the wrappers to unwrap the contained object run-time. If the type of the template argument matches the object the unwrap succeeds and I can rewrap it into a Printable or just directly use the overloaded print() free function.
template<typename EncodableT>
EncodableT* get() const {
EncodableModel<EncodableT>* modelPtr = dynamic_cast<EncodableModel<EncodableT>*> pimpl.get());
if (modelPtr == nullptr) return nullptr;
return &modelPtr->_inst;
}
However the usage of this function would be rather ugly and inconvenient:
for (Encodable const& obj : readEncodables) {
auto* ptr1 = obj.get<MyStruct1>();
if (ptr1 != nullptr) {
print(*ptr1);
continue;
}
auto* ptr2 = obj.get<MyStruct2>();
if (ptr2 != nullptr) {
print(*ptr2);
continue;
}
// ...
}
This could be made a bit nicer by storing std::type_index the wrappers, but I'd still have create if-else or switch statements for each type for calling a simple print() free function...
I have the generic class which can store a value and a type of the value encoded as a string.
#include <iostream>
#include <string>
#include <memory>
#include <cassert>
struct IValueHolder
{
virtual ~IValueHolder() = default;
virtual const std::string& getType() = 0;
virtual void setType(const std::string& t_type) = 0;
};
template<class T>
class ValueHolder : public IValueHolder
{
private:
T m_value;
std::string m_type;
public:
ValueHolder(T t_value) : m_value(t_value){}
virtual const std::string& getType() override
{
return m_type;
}
virtual void setType(const std::string& t_type) override
{
m_type= t_type;
}
const T& getValue() const
{
return m_value;
}
};
std::unique_ptr<IValueHolder> build_int_property()
{
auto p32{ std::make_unique<ValueHolder<int32_t>>(0) };
p32->setType("int32");
return std::move(p32);
}
int main()
{
auto v32 = dynamic_cast<ValueHolder<int32_t>*>(build_int_property().get());
assert(v32->getValue() == 0); // FAILS
assert(v32->getType() == "int32"); // FAILS
return EXIT_SUCCESS;
}
And I have another utility function build_int_property which builds an integer property. But unfortunately, the tests fail. Can anyone explain what is going wrong here?
I have to pass around the void * for supporting types that can't be known at compile time, but I also don't want to go totally insane and left everything on myself so I think to use type_info for type_Checking but since type_info doesn't support copy operation I am getting compiler errors when passing them around
#include <bits/stdc++.h>
struct PropertyClassInterface {
virtual const char * name() = 0;
virtual void set(const char * property_name,std::pair<const void *,std::type_info> new_val) = 0;
virtual std::pair<std::shared_ptr<void>,std::type_info> get(const char * property_name) = 0;
template< typename Type>
static std::pair<std::shared_ptr<void>,std::type_info> get_type_pair(const Type& Value) {
std::shared_ptr<void> t = std::make_shared<Type>();
*static_cast<Type *>(t.get()) = Value;
*return std::make_pair(t,typeid(Type));* // error
}
};
struct PropertyManager {
using base_pointer = std::shared_ptr<PropertyClassInterface>;
void add_object(base_pointer new_member) {
objects.push_back(new_member);
}
template<typename Type>
void set(const char * object_name,const char * property_name,const Type& new_val) {
find_object_orThrow(object_name)->set(property_name,std::make_pair(static_cast<const void *>(&new_val),typeid(new_val)));
}
template<typename Type>
Type get(const char * object_name, const char * property_name) {
auto a = find_object_orThrow(object_name)->get(property_name);
if (typeid(Type).hash_code() != a.second.hash_code())
throw std::runtime_error{"get(): mismatched type"};
return a.first;
}
public:
std::vector<base_pointer> objects;
base_pointer find_object_orThrow(const char * obj_name){
for(auto& o : objects) {
if (!strcmpi(o->name(),obj_name)) {
return o;
}
}
throw std::runtime_error{std::string("no object named \"") + obj_name + "\" found"};
}
};
struct testClass : PropertyClassInterface {
void set(const char * property_name,std::pair<const void *,std::type_info> new_val) {
auto checkTypeInfo = [&new_val](const std::type_info& expected) {
if (new_val.second.hash_code() != expected.hash_code())
throw std::runtime_error{"set(): wrong type"};
};
if (!strcmpi(property_name,"my_number")) {
checkTypeInfo(typeid(decltype(my_number)));
my_number = *static_cast<const decltype(my_number) *>(new_val.first);
}
};
std::pair<std::shared_ptr<void>,std::type_info> get(const char * property_name) {
if (!strcmpi(property_name,"my_number")) {
PropertyClassInterface::get_type_pair(my_number);
}
}
private:
int my_number;
};
int main() {
};
so do I have to use dynamic memory for storing type_info as well
I am limited to c++11 and I know about not using bits headers and am only using for testing
What you want to do is implement an any, or use boost any.
An any isn't hard to write.
namespace details {
struct any_concept;
using pimpl=std::unique_ptr<any_concept>;
struct any_concept {
virtual ~any_concept() {}
virtua pimpl clone() const = 0;
virtual std::type_info const& type() const = 0;
private:
virtual void* unsafe_get() = 0;
virtual void const* unsafe_get() const = 0;
public:
template<class T>
T* get() {
if (typeid(T) != type()) return nullptr;
return static_cast<T*>(unsafe_get());
}
template<class T>
T const* get() const {
if (typeid(T) != type()) return nullptr;
return static_cast<T const*>(unsafe_get());
}
};
template<class T>
struct any_model:any_concept {
T t;
virtual ~any_model() = default;
virtual pimpl clone() const final override {
return pimpl( new any_model(t) );
}
virtual std::type_info const& type() const final override {
return typeid(T);
}
template<class U>
any_model(U&& u):t(std::forward<U>(u)){}
private:
virtual void* unsafe_get() final override { return std::addressof(t); }
virtual void const* unsafe_get() const final override { return std::addressof(t); }
};
}
struct any {
template<class T, typename std::enable_if<!std::is_same<any, typename std::decay<T>::type>::value, bool> =true>
any( T&& t ):
pImpl( new details::any_model<typename std::decay<T>::type>( std::forward<T>(t) ) )
{}
template<class T>
T* get() {
if (!pImpl) return nullptr;
return pImpl->get<T>();
}
template<class T>
T const* get() const {
if (!pImpl) return nullptr;
return const_cast<details::any_concept const&>(*pImpl).get<T>();
}
template<class T>
bool contains()const { return get<T>(); }
explicit operator bool() const {
return (bool)pImpl;
}
any()=default;
any(any&&)=default;
any& operator=(any&&)=default;
~any()=default;
any(any const& o):
pImpl( o.pImpl?o.pImpl->clone():pimpl{} )
{}
any& operator=(any const& o) {
any tmp(o);
std::swap(*this, tmp);
return *this;
}
private:
details::pimpl pImpl;
};
there; a really simple any implementation. Written on a phone, so probably contains typos.
It supports value semantics, but store anything (that can be copied and destroyed). If you know what it stores, you can .get<T>() it. You can ask it if it contains<T>() as well.
This is known as a vocabulary type. It is basically your void* and type info bundled in a way that makes misuse more difficult.
I have many functions that do roughly the same apart from the what variable the modify
struct example
{
std::string name;
std::string category;
};
using ObjName = std::string;
using Value = std::string;
bool updateName(const ObjName &name, const Value& value) ...
bool updateCategory(const ObjName &name,const Value& value)
{
// boost optional pointing to struct reference
auto obj = findOjb(name);
if (obj)
{
obj.get().category = value; // variable name changes
return true;
}
return false;
}
What I am wondering is what I can do to combine the code ?
I suspect it will involve templates maybe traites/functors but I am unsure of how to approach it any ideas ?
Reworking Daerst's code to remove that awful offsetof in favor of pointers-to-members...
struct example
{
std::string name;
std::string category;
};
bool updateVariable(const ObjName &name, std::string example::*member, std::string const &value)
{
// your code ...
// Access
rule.get().*member = value
// rest of your code
}
bool updateName(const ObjName &oldname, const ObjName& newName)
{
return updateVariable(name, &example::name, newName));
}
bool updateCategory(const ObjName &name, Category &cat)
{
return updateVariable(name, &example::category, cat));
}
You could use lambdas:
template <typename Accessor>
bool updateVariable(const ObjName& name, const Value& value, Accessor access) {
auto obj = findObj(name);
if (obj)
{
access(obj.get()) = value;
return true;
}
return false;
}
bool updateCategory(const ObjName& name, const Value& value) {
return updateVariable(name, value,
[](Example& e) -> Value& { return e.category; });
}
This is a bit more flexible than the pointer-to-member solution. You can make it even more flexible by having the lambda do the setting instead of returning a reference.
You could use something traits like:
#include <string>
#include <assert.h>
struct example
{
std::string name;
int category;
};
struct nameDesc
{
typedef std::string valuetype;
static void set(example& obj, const valuetype& val)
{
obj.name = val;
}
};
struct categoryDesc
{
typedef int valuetype;
static void set(example& obj, const valuetype& val)
{
obj.category = val;
}
};
example test; // just for testing...
example& findObj(const std::string &name)
{
// just for testing...
return test;
}
template <typename V>
bool update(const std::string &objName, const typename V::valuetype& value)
{
example& obj = findObj(objName);
V::set(obj, value);
return true;
}
bool updateName(const std::string &objName, const std::string& value) { return update<nameDesc>(objName, value); }
bool updateCategory(const std::string &objName, int value) { return update<categoryDesc>(objName, value); }
int main()
{
update<nameDesc>("objname", "asdf");
update<categoryDesc>("objname", 1234);
assert(test.name == "asdf");
assert(test.category == 1234);
updateName("objname", "qwer");
updateCategory("objname", 7890);
assert(test.name == "qwer");
assert(test.category == 7890);
return 0;
}
And I'd encourage you having a look at boost::spirit / BOOST_FUSION_ADAPT_STRUCT if possible.
A bit hacky, but this could be a solution (untested code) using offsetof:
struct example
{
std::string name;
std::string category;
};
bool updateVariable(const size_t offset, std::string value)
{
// your code ...
// ASSIGNMENT: get address, apply offset and assign value
*(&rule.get() + offset) = cat;
// rest of your code
}
bool updateName(const ObjName &oldname, const ObjName& newName)
{
return updateVariable(offsetof(struct example, name), newName));
}
bool updateCategory(const ObjName &name, Category &cat)
{
return updateVariable(offsetof(struct example, category), cat));
}
I assume that ObjName and Category are typedefs of string or can be implicitly converted.
You still need to add a one-liner function for each member variable, which is pretty hard to elude in C++ if you want to stick to a hard-coded struct. You might want to consider converting the whole struct definition to data, e.g. loaded from a file, opening other possibilities.
Basically i just want to do an arbitrary operation using given arguments of arbitrary types.
Argument type base class is Var, and Operation is base class of the operation that will executed for given arguments.
I have Evaluator class, that hold a collection of operators which mapped using opId. Evaluator will do operation based on opId argument given in evaluate() member function, then evaluate() function will do search for supported operator that will accept argument type and opId.
what I want to ask is, is there any efficient pattern or algorithm that will do this without dynamic_cast<> and/or looping through operator collection.
`
class Var {
public:
bool isValidVar();
static Var invalidVar();
}
template<typename T> class VarT : public Var {
public:
virtual const T getValue() const;
}
class Operator {
public:
virtual Var evaluate(const Var& a, const Var& b) = 0;
}
template<typename T> class AddOperator : public Operator {
public:
virtual Var evaluate(const Var& a, const Var& b)
{ //dynamic_cast is slow!
const VarT<T>* varA = dynamic_cast<const VarT<T>*>(&a);
const VarT<T>* varB = dynamic_cast<const VarT<T>*>(&b);
if(varA && varB) //operation supported
{
return VarT<T>(varA->getValue() + varA->getValue());
}
return Var::invalidVar(); //operation for this type is not supported
}
}
class Evaluator {
private:
std::map<int,std::vector<Operator>> operatorMap;
public:
virtual Var evaluate(const Var& a, const Var& b,int opId)
{
std::map<int,std::vector<Operator>>::iterator it = this->operatorMap.find(opId);
if(it != this->operatorMap.end())
{
for(size_t i=0 ; i<it->second.size() ; i++)
{
Var result = it->second.at(i).evaluate(a,b);
if(result.isValidVar())
{
return result;
}
}
}
//no operator mapped, or no operator support the type
return Var::invalidVar();
}
}
`
if you do not want to use dynamic_cast, consider adding type traits into your design.
Added 05/03/10 : The following sample will demonstrate how runtime-traits works
CommonHeader.h
#ifndef GENERIC_HEADER_INCLUDED
#define GENERIC_HEADER_INCLUDED
#include <map>
#include <vector>
#include <iostream>
// Default template
template <class T>
struct type_traits
{
static const int typeId = 0;
static const int getId() { return typeId; }
};
class Var
{
public:
virtual ~Var() {}
virtual int getType() const = 0;
virtual void print() const = 0;
};
template<typename T>
class VarT : public Var
{
T value;
public:
VarT(const T& v): value(v) {}
virtual int getType() const { return type_traits<T>::getId(); };
virtual void print() const { std::cout << value << std::endl; };
const T& getValue() const { return value; }
};
class Operator
{
public:
virtual ~Operator() {}
virtual Var* evaluate(const Var& a, const Var& b) const = 0;
};
template<typename T>
class AddOperator : public Operator
{
public:
virtual Var* evaluate(const Var& a, const Var& b) const
{
// Very basic condition guarding
// Allow operation within similar type only
// else have to create additional compatibility checker
// ie. AddOperator<Matrix> for Matrix & int
// it will also requires complicated value retrieving mechanism
// as static_cast no longer can be used due to unknown type.
if ( (a.getType() == b.getType()) &&
(a.getType() == type_traits<T>::getId()) &&
(b.getType() != type_traits<void>::getId()) )
{
const VarT<T>* varA = static_cast<const VarT<T>*>(&a);
const VarT<T>* varB = static_cast<const VarT<T>*>(&b);
return new VarT<T>(varA->getValue() + varB->getValue());
}
return 0;
}
};
class Evaluator {
private:
std::map<int, std::vector<Operator*>> operatorMap;
public:
void registerOperator(Operator* pOperator, int iCategory)
{
operatorMap[iCategory].push_back( pOperator );
}
virtual Var* evaluate(const Var& a, const Var& b, int opId)
{
Var* pResult = 0;
std::vector<Operator*>& opList = operatorMap.find(opId)->second;
for ( std::vector<Operator*>::const_iterator opIter = opList.begin();
opIter != opList.end();
opIter++ )
{
pResult = (*opIter)->evaluate( a, b );
if (pResult)
break;
}
return pResult;
}
};
#endif
DataProvider header
#ifdef OBJECTA_EXPORTS
#define OBJECTA_API __declspec(dllexport)
#else
#define OBJECTA_API __declspec(dllimport)
#endif
// This is the "common" header
#include "CommonHeader.h"
class CFraction
{
public:
CFraction(void);
CFraction(int iNum, int iDenom);
CFraction(const CFraction& src);
int m_iNum;
int m_iDenom;
};
extern "C" OBJECTA_API Operator* createOperator();
extern "C" OBJECTA_API Var* createVar();
DataProvider implementation
#include "Fraction.h"
// user-type specialization
template<>
struct type_traits<CFraction>
{
static const int typeId = 10;
static const int getId() { return typeId; }
};
std::ostream& operator<<(std::ostream& os, const CFraction& data)
{
return os << "Numerator : " << data.m_iNum << " # Denominator : " << data.m_iDenom << std::endl;
}
CFraction operator+(const CFraction& lhs, const CFraction& rhs)
{
CFraction obj;
obj.m_iNum = (lhs.m_iNum * rhs.m_iDenom) + (rhs.m_iNum * lhs.m_iDenom);
obj.m_iDenom = lhs.m_iDenom * rhs.m_iDenom;
return obj;
}
OBJECTA_API Operator* createOperator(void)
{
return new AddOperator<CFraction>;
}
OBJECTA_API Var* createVar(void)
{
return new VarT<CFraction>( CFraction(1,4) );
}
CFraction::CFraction() :
m_iNum (0),
m_iDenom (0)
{
}
CFraction::CFraction(int iNum, int iDenom) :
m_iNum (iNum),
m_iDenom (iDenom)
{
}
CFraction::CFraction(const CFraction& src) :
m_iNum (src.m_iNum),
m_iDenom (src.m_iDenom)
{
}
DataConsumer
#include "CommonHeader.h"
#include "windows.h"
// user-type specialization
template<>
struct type_traits<int>
{
static const int typeId = 1;
static const int getId() { return typeId; }
};
int main()
{
Evaluator e;
HMODULE hModuleA = LoadLibrary( "ObjectA.dll" );
if (hModuleA)
{
FARPROC pnProcOp = GetProcAddress(hModuleA, "createOperator");
FARPROC pnProcVar = GetProcAddress(hModuleA, "createVar");
// Prepare function pointer
typedef Operator* (*FACTORYOP)();
typedef Var* (*FACTORYVAR)();
FACTORYOP fnCreateOp = reinterpret_cast<FACTORYOP>(pnProcOp);
FACTORYVAR fnCreateVar = reinterpret_cast<FACTORYVAR>(pnProcVar);
// Create object
Operator* pOp = fnCreateOp();
Var* pVar = fnCreateVar();
AddOperator<int> intOp;
AddOperator<double> doubleOp;
e.registerOperator( &intOp, 0 );
e.registerOperator( &doubleOp, 0 );
e.registerOperator( pOp, 0 );
VarT<int> i1(10);
VarT<double> d1(2.5);
VarT<float> f1(1.0f);
std::cout << "Int Obj id : " << i1.getType() << std::endl;
std::cout << "Double Obj id : " << d1.getType() << std::endl;
std::cout << "Float Obj id : " << f1.getType() << std::endl;
std::cout << "Import Obj id : " << pVar->getType() << std::endl;
Var* i_result = e.evaluate(i1, i1, 0); // result = 20
Var* d_result = e.evaluate(d1, d1, 0); // no result
Var* f_result = e.evaluate(f1, f1, 0); // no result
Var* obj_result = e.evaluate(*pVar, *pVar, 0); // result depend on data provider
Var* mixed_result1 = e.evaluate(f1, d1, 0); // no result
Var* mixed_result2 = e.evaluate(*pVar, i1, 0); // no result
obj_result->print();
FreeLibrary( hModuleA );
}
return 0;
}
If you can modify the type Var you could add type-Ids for the argument types. But in the implementation of your operations you would always have to use a dynamic_cast at some point. If your types and operations are fixed at compile-time, you can do the whole thing with templates using Boost.MPL (specifically the containers).
Your sample code contains many errors, including slicing problems.
I'm not 100% sure, but I seem to remember you can use const type_info* as a key for a map.
If so, you could use something like following. It is not free from RTTI (type_info), but since Evaluator already checks the typeids, you can use a static_cast instead of a dynamic_cast (but it isn't that important now that the code doesn't blindly search for the right operator to apply).
Of course, the following is completely broken in terms of memory management. Reimplement with smart pointers of your choice.
#include <map>
#include <typeinfo>
#include <cassert>
#include <iostream>
struct CompareTypeinfo
{
bool operator()(const std::type_info* a, const std::type_info* b) const
{
return a->before(*b);
}
};
class Var {
public:
virtual ~Var() {}
virtual const std::type_info& getType() const = 0;
virtual void print() const = 0;
};
template<typename T> class VarT : public Var {
T value;
public:
VarT(const T& v): value(v) {}
const T& getValue() const { return value; }
virtual const std::type_info& getType() const { return typeid(T); }
virtual void print() const { std::cout << value << '\n'; }
};
class Operator {
public:
virtual ~Operator() {}
virtual Var* evaluate(const Var& a, const Var& b) const = 0;
virtual const std::type_info& getType() const = 0;
};
template<typename T> class AddOperator : public Operator {
public:
typedef T type;
virtual const std::type_info& getType() const { return typeid(T); }
virtual Var* evaluate(const Var& a, const Var& b) const
{
//it is the responsibility of Evaluator to make sure that the types match the operator
const VarT<T>* varA = static_cast<const VarT<T>*>(&a);
const VarT<T>* varB = static_cast<const VarT<T>*>(&b);
return new VarT<T>(varA->getValue() + varB->getValue());
}
};
class Evaluator {
private:
typedef std::map<const std::type_info*, Operator*, CompareTypeinfo> TypedOpMap;
typedef std::map<int, TypedOpMap> OpMap;
OpMap operatorMap;
public:
template <class Op>
void registerOperator(int opId)
{
operatorMap[opId].insert(std::make_pair(&typeid(typename Op::type), new Op));
}
Var* evaluate(const Var& a, const Var& b,int opId)
{
OpMap::const_iterator op = operatorMap.find(opId);
if (op != operatorMap.end() && a.getType() == b.getType()) {
TypedOpMap::const_iterator typed_op = op->second.find(&a.getType());
if (typed_op != op->second.end()) {
//double-checked
assert(typed_op->second->getType() == a.getType());
return typed_op->second->evaluate(a, b);
}
}
return 0;
}
};
int main()
{
Evaluator e;
e.registerOperator<AddOperator<int> >(0);
e.registerOperator<AddOperator<double> >(0);
VarT<int> i1(10), i2(20);
VarT<double> d1(2.5), d2(1.5);
VarT<float> f1(1.0), f2(2.0);
Var* i_result = e.evaluate(i1, i2, 0);
Var* d_result = e.evaluate(d1, d2, 0);
Var* f_result = e.evaluate(f1, f2, 0);
Var* mixed_result = e.evaluate(i1, d2, 0);
assert(i_result != 0);
assert(d_result != 0);
assert(f_result == 0); //addition not defined for floats in Evaluator
assert(mixed_result == 0); //and never for mixed types
i_result->print(); //30
d_result->print(); //4.0
}