Class design - use optionals? variants? be opaque? - c++

I want to have a class for PCI bus locations. For the sake of discussion, these come in three forms:
[domain]:[bus]:[device].[function]
[domain]:[bus]:[device]
[bus]:[device].[function]
and let's say each field is a non-negative integral value (let's even say unsigned just to make things simple).
I'm scratching my head regarding how to define this class. I could use std::optionals for the domain and function fields; but then, they're not both optional. I could use a variant with 3 types, but then I need to define separate types, which overlap a lot. I could just hold 4 unsigneds and a 3-value enum for which format is in effect - but that's quite a bit of hassle, and I'd need getter and to make the class opaque. Same thing if I try to use a union somehow.
It seems like every choice I make, it's going to be an iffy class. How can I minimize my displeasure with it?
Note: Any language standard version is ok for the answer, although I doubt C++20 would give you anything.

Building upon my comment, I was wondering if something like this could work:
enum class pci_format { domain_function, domain, function };
template <pci_format E> struct tag { };
class pci_location {
public:
pci_location (tag<pci_format::domain_function>, unsigned domain, unsigned bus,
unsigned device, unsigned function)
: format_(pci_format::domain_function)
, domain_(domain)
, bus_(bus)
, device_(device)
, function_(function)
{ }
// Repeat for other values of pci_format.
pci_format format () const { return format_; }
bool has_domain () const {
return (format_ == pci_format::domain_function)
or (format_ == pci_format::domain);
}
unsigned domain () const {
if (not has_domain()) { throw std::runtime_error("Domain not available."); }
return domain_;
}
// Repeat for other fields.
private:
pci_format format_;
unsigned domain_;
unsigned bus_;
unsigned device_;
unsigned function_
};
You would basically create a specific constructor for each PCI "format". Of course you could also store each unsigned as an std::optional<unsigned>, but that would force users to "dereference" each optional even if they knew for sure that it must contain a value.
One way or another, they'll have to check what "format" the location is in, so it seems to me that using an enum for this is more user friendly. Then users only have to check once and know exactly which fields are available.
I guess you could layer a visitor on top of all this so they can simply provide code to execute for each "format":
struct pci_location_visitor {
virtual void visit (tag<pci_format::domain_function>, pci_location const & obj) = 0;
// Repeat for other enum values.
};
// Add to pci_location:
void accept (pci_location_visitor & visitor) {
switch (format_) {
case pci_format::domain_function:
return visitor.visit(tag<pci_format::domain_function>{}, *this);
default: throw std::runtime_error("Format not supported for visitation.");
}
}
Then on top of that you could create a visitor that can be constructed from a bunch of callables, i.e. lambdas, so that this all can be used like below:
pci_location const & loc = getIt();
auto printSomething = make_pci_location_visitor(
[](tag<pci_format::domain_function>, pci_location const & e) { std::cout << e.domain(); }
, [](tag<pci_format::domain>, pci_location const & e) { std::cout << e.bus(); }
, [](tag<pci_format::function>, pci_location const & e) { std::cout << e.function(); }
);
loc.accept(printSomething);
For an example of how such a visitor could be constructed, see the overloaded class in the std::visit example on cppreference.com.

As requested in comments... given that I have no particular requirements how the users would prefer to use this class, given C++14, I would be doing something generic along the lines of:
#include <array>
#include <climits>
#include <iostream>
#include <stdexcept>
class pci_location_t {
public:
struct dbdf {
unsigned int domain;
unsigned int bus;
unsigned int device;
unsigned int function;
};
struct dbd {
unsigned int domain;
unsigned int bus;
unsigned int device;
};
struct bdf {
unsigned int bus;
unsigned int device;
unsigned int function;
};
pci_location_t(dbdf v) : domain(v.domain), bus(v.bus), device(v.device), function(v.function) {}
pci_location_t(dbd v) : domain(v.domain), bus(v.bus), device(v.device), function(INVALID) {}
pci_location_t(bdf v) : domain(INVALID), bus(v.bus), device(v.device), function(v.function) {}
template <typename dbdf_f, typename dbd_f, typename bdf_f>
auto visit(dbdf_f dbdf_fun, dbd_f dbd_fun, bdf_f bdf_fun) const {
if (domain == INVALID) {
if (function == INVALID) {
throw std::domain_error("Wrong PCI location format");
}
return bdf_fun(bdf{bus, device, function});
} else if (function == INVALID) {
return dbd_fun(dbd{domain, bus, device});
} else {
return dbdf_fun(dbdf{domain, bus, device, function});
}
}
private:
friend pci_location_t invalid_location();
pci_location_t() : domain(INVALID), bus(INVALID), device(INVALID), function(INVALID) {}
const static unsigned int INVALID = UINT_MAX;
unsigned int domain;
unsigned int bus;
unsigned int device;
unsigned int function;
};
pci_location_t invalid_location() { return pci_location_t{}; }
int main() {
std::array<pci_location_t, 4> locations = {
pci_location_t(pci_location_t::dbdf{1, 2, 3, 4}),
pci_location_t(pci_location_t::dbd{1, 2, 3}),
pci_location_t(pci_location_t::bdf{2, 3, 4}),
invalid_location()
};
try {
for (auto& l : locations) {
l.visit(
[] (auto dbdf) {
std::cout << dbdf.domain << ":" << dbdf.bus << ":" << dbdf.device << "." << dbdf.function << std::endl;
},
[] (auto dbd) {
std::cout << dbd.domain << ":" << dbd.bus << ":" << dbd.device << std::endl;
},
[] (auto bdf) {
std::cout << bdf.bus << ":" << bdf.device << "." << bdf.function << std::endl;
}
);
}
std::cout << "Done!" << std::endl;
} catch(const std::exception& e) {
std::cout << e.what() << std::endl;
}
return 0;
}
(you can check it on Coliru).
Feel free to use optionals or a separate format field if you don't like special values.

I'd make both the domain and the function optional (I don't really care how, as long as it's effective), and just enforce the only-one-missing condition as a class invariant. That is, only the functions that can change any of the fields need to perform the check and signal possible errors back to the user. No need to bloat your code with variants, or with dynamically interpreted unsigned int arrays. KISS.

Related

Call static member function by pointer

I'm working on a small custom Assembler
I have a vector of struc to storing OPCODE informations (Mnemonic, number and type of argument, parsing function,...)
typedef char args_type_t;
typedef struct op_s {
std::string opcode;
char nbr_args;
args_type_t type[4];
int code;
Tryte (*fct)(std::vector<std::string>);
} op_t;
the parsing function is pointer on a static member function :
{"MOV", 2, {T_REGISTER | T_ADDRESS, T_REGISTER | T_ADDRESS | T_CONSTANT}, 1, &Opcodes::MOV},
and the function :
class Opcodes
{
public:
static Tryte Opcodes::MOV(std::vector<std::string> _opMap) {
return Tryte(0);
}
};
I try this, but I get SEGFAULT (str() is a member function of Tryte) :
for (int i = 0; i < opMap.size(); i++) {
for (int j = 0; j < op_tab.size(); j++) {
if (!op_tab[j].opcode.compare(opMap[i][2])) {
std::cout << "OPBYTE : " << op_tab[j].fct(opMap[i]).str() << std::endl;
}
}
}
I want to call my function without instanciate Opcodes object it's posible ?
EDIT :
my error was here : if (!op_tab[j].opcode.compare(opMap[i][2]))
my mnemonic is the 1st item n opMap
Your code seems right, so perhaps a debugger information could help a bit.
But we can try to improve the friendlyness of the code by using a std::function:
typedef char args_type_t;
#include <functional>
typedef struct op_s {
std::string opcode;
char nbr_args;
args_type_t type[4];
int code;
std::function<Tryte(std::vector<std::string>>)> fct;
} op_t;
As for the sefgault, send us the backtrace.
Also, try to use range-based-for as it doesn't needs to tranverse the map to get the element again (as you are doing inside of the inner loop)
for (auto op : opMap) {
for (auto tab : op_tab) {
if (!tab.opcode.compare(op[1])) {
std::cout << "OPBYTE : " << tab.fct(op).str() << std::endl;
}
}
}
One common fix that you can do to not miss the indexes anymore is to use an Enum holding the possibilities.
enum Columns {
FuncPointer,
UserData
}
for (auto op : opMap) {
for (auto tab : op_tab) {
if (!tab.opcode.compare(op[FuncPointer])) {
std::cout << "OPBYTE : " << tab.fct(op).str() << std::endl;
}
}
}

Correct way of unpacking operation type from network application

I come from python world, and as a weekend project I decided to write a simple UDP server in c++. I have a question regarding correct way of discovering the type of incoming request. My approach is to have a class for every possible type of request. Upon packet arrival I have to unpack it's OPID (operation id) and instantiate correct class. To do that I have to bind OPIDs with the classes, and the only way I'm familiar of doing this in c++ involves huge switch:case block. Doing this doesn't really feels right for me, also If I understand UncleBob correctly, this goes against few OOP practices. As code describes the best one's intentions, here's python equivalent of what I'm trying to do with c++.
class BaseOperation:
OPID = 0
def process(packet_data):
raise NotImplementedError("blah blah")
class FooOperation(BaseOperation):
OPID = 1
def process(packet_data):
print("Foo on the packet!")
class BarOperation(BaseOperation):
OPID = 2
def process(packet_data):
print("Bar on the packet!")
opid_mappings = {
FooOperation.OPID: FooOperation,
BarOperation.OPID: BarOperation
}
Somewhere in code handling the incoming packet
def handle_connection(packet):
try:
operation = opid_mappings[get_opid(packet)]()
except KeyError:
print("Unknown OPID")
return
operation.process(get_data(packet))
Really quick hack of object-based solution. This might not be the right way to go in our wonderful new C++11 world of std::function.
If the children of BaseOperation need to store state, go objects!
#include <iostream>
#include <map>
class BaseOperation
{
protected:
int OPID;
public:
virtual ~BaseOperation()
{
}
virtual int operator()() = 0;
};
class FooOperation:public BaseOperation
{
public:
static constexpr int OPID = 1;
FooOperation()
{
}
int operator()()
{
// do parsing
return OPID; // just for convenience so we can tell who was called
}
};
constexpr int FooOperation::OPID; // allocate storage for static
class BarOperation:public BaseOperation
{
public:
static constexpr int OPID = 2;
BarOperation()
{
}
int operator()()
{
// do parsing
return OPID; // just for convenience so we can tell who was called
}
};
constexpr int BarOperation::OPID; // allocate storage for static
std::map<int, BaseOperation*> opid_mappings{
{FooOperation::OPID, new FooOperation()},
{BarOperation::OPID, new BarOperation()}
};
int main()
{
std::cout << "calling OPID 1:" << (*opid_mappings[1])() << std::endl;
std::cout << "calling OPID 2:" << (*opid_mappings[2])() << std::endl;
for (std::pair<int, BaseOperation*> todel: opid_mappings)
{
delete todel.second;
}
return 0;
}
This also ignores the fact that there is probably no need for the map. If the OPIDs are sequential, a good ol' dumb array solves the problem. I like the map because it won't screw up if someone moves a parser handler or inserts one into the middle of the list.
Regardless, this has a bunch of memory management problems, such as the need for the for loop deleting the parser objects at the bottom of main. This could be solved with std::unique_ptr, but this is probably a rabbit hole we don't need to go down.
Odds are really good that the parser doesn't have any state and we can just use a map of OPIDs and std::function.
#include <iostream>
#include <map>
#include <functional>
static constexpr int FooOPID = 1;
int fooOperation()
{
// do parsing
return FooOPID;
}
static constexpr int BarOPID = 2;
int BarOperation()
{
// do parsing
return BarOPID;
}
std::map<int, std::function<int()>> opid_mappings {
{FooOPID, fooOperation},
{BarOPID, BarOperation}
};
int main()
{
std::cout << "calling OPID 1:" << opid_mappings[1]() << std::endl;
std::cout << "calling OPID 2:" << opid_mappings[2]() << std::endl;
return 0;
}
And because the parser's are kind of useless if you aren't passing anything in, one last tweak:
#include <iostream>
#include <map>
#include <functional>
struct Packet
{
//whatever you need here. Probably a buffer reference and a length
};
static constexpr int FooOPID = 1;
int fooOperation(Packet & packet)
{
// do parsing
return FooOPID;
}
static constexpr int BarOPID = 2;
int BarOperation(Packet & packet)
{
// do parsing
return BarOPID;
}
std::map<int, std::function<int(Packet &)>> opid_mappings {
{FooOPID, fooOperation},
{BarOPID, BarOperation}
};
int main()
{
Packet packet;
std::cout << "calling OPID 1:" << opid_mappings[1](packet) << std::endl;
std::cout << "calling OPID 2:" << opid_mappings[2](packet) << std::endl;
return 0;
}

de-serialize ASCII to struct

I have come up with the following structure to declare various formats if messages that are to be received from the network:
#include <stdint.h>
#include <iostream>
#include <string.h>
template<int T>
struct uint
{
static uint<T> create(uint64_t value)
{
uint<T> r = {value};
return r;
}
uint(uint64_t value)
{
v = value;
}
uint()
{}
uint<T>& operator =(uint64_t value)
{
v = value;
return *this;
}
operator uint64_t() const
{
return (uint64_t)v;
}
unsigned long long v:T;
}__attribute__((packed));
example:
typedef uint<5> second_t;
suppose one of the message formats (which are auto-generated via some process) is like this:
struct seconds
{
char _type;
second_t _second;
} __attribute__((packed));
Now suppose I would like to populate an instance of the above messahe using a string:
int main()
{
seconds ii;
const char *i = "123456";
// memset, memcpy,sprintf... ??? what to use here?
std::cout << ii._type << " " << ii._second << std::endl;
}
Given a stream 123456, I expect the instance of the seconds (ii) structure to have char ii._type = '1' and integer ii._second = 23456. But I dont know how to do that. Do you have a clue how i can do that? and do you have any suggestion how to improve the basic structure?
thanks
You have a number of easier and more reliable options available that require almost no work.
check out google protocol buffers (platform independent message serialisation and deserialisation): https://developers.google.com/protocol-buffers/
or boost::serialization - (probably faster, but not platform-independant) http://www.boost.org/doc/libs/1_58_0/libs/serialization/doc/index.html

Order of decorations in Decorator Pattern

Most of you know the pizza / cofee example for the decorator pattern.
Pizza* pizza1 = BigPizzaDecorator(MushromDecorator(SimplePizza()));
Pizza* pizza2 = MushromDecorator(BigPizzaDecorator(SimplePizza()));
the two object behave in a similar way, but not completely, in particular if you have non-commutative operation, for example:
BigPizzaDecorator::price() { return 10 + PizzaDecorator::price(); } // this is commutative
BigPizzaDecorator::name() { return "big " + PizzaDecorator::name(); } // this is not commutative
So the price for pizza1 and pizza2 are the same, but the name is not, for example the first should be "Big mushroom pizza", the second "Mushroom big pizza". The first is english correct (probably better would be "Big pizza with mushroom", but it's not so important).
The book "Head first" point out this problem with the Cofee example:
When you need to peek at multiple layers into the decorator chain, you
are starting to push the decorator beyond its true intent.
Nevertheless, such things are possible. Imagine a CondimentPrettyPrint
decorator that parses the final decription and can print “Mocha, Whip,
Mocha” as “Whip, Double Mocha.”
what is the best way to do that? (operator< ?)
Ive never known this sort of thing to be needed when using decorators. And I would think that if you need to do this, then you shouldn't be using decorators, especially as you're knowingly "pushing the decorator beyond it's intent".
I have had a stab at doing this, the code is below. Basically, I create a thin layer around the SimplePizza object that understands what the decorators need, then the decorators decorate that.
The main problem here, is that to maintain order in the output, you would have to maintain a relationship between decorators - which can quickly become a maintenance nightmare.
#include <iostream>
#include <queue>
#include <sstream>
struct name_part
{
std::string mName;
int mPriority;
name_part(const std::string& name, int priority)
: mName(name)
, mPriority(priority)
{
}
};
bool operator<(const name_part& a, const name_part& b)
{
return (a.mPriority < b.mPriority);
}
std::string priority_queueToString(const std::priority_queue<name_part>& orig)
{
std::ostringstream oss;
std::priority_queue<name_part> q(orig);
while (!q.empty())
{
oss << q.top().mName << " ";
q.pop();
}
return oss.str();
}
struct SimplePizza
{
virtual std::string name()
{
return "pizza";
}
};
struct SimplePizzaImplementer : SimplePizza
{
SimplePizza *mDecorated;
SimplePizzaImplementer()
: mDecorated(0)
{
}
SimplePizzaImplementer(SimplePizza *decorated)
: mDecorated(decorated)
{
}
virtual std::string name()
{
return priority_queueToString(nameParts());
}
virtual std::priority_queue<name_part> nameParts()
{
std::priority_queue<name_part> q;
if (mDecorated)
{
q.push(name_part(mDecorated->name(), 0));
}
return q;
}
};
struct MushroomDecorator : SimplePizzaImplementer
{
SimplePizzaImplementer *mDecorated;
MushroomDecorator(SimplePizzaImplementer *decorated)
: mDecorated(decorated)
{
}
virtual std::string name()
{
return priority_queueToString(nameParts());
}
virtual std::priority_queue<name_part> nameParts()
{
std::priority_queue<name_part> q = mDecorated->nameParts();
q.push(name_part("mushroom", 1));
return q;
}
};
struct BigDecorator : SimplePizzaImplementer
{
SimplePizzaImplementer *mDecorated;
BigDecorator(SimplePizzaImplementer *decorated)
: mDecorated(decorated)
{
}
virtual std::string name()
{
return priority_queueToString(nameParts());
}
virtual std::priority_queue<name_part> nameParts()
{
std::priority_queue<name_part> q = mDecorated->nameParts();
q.push(name_part("big", 2));
return q;
}
};
int main()
{
SimplePizzaImplementer *impl = new SimplePizzaImplementer(new SimplePizza());
SimplePizza *pizza1 = new MushroomDecorator(new BigDecorator(impl));
SimplePizza *pizza2 = new BigDecorator(new MushroomDecorator(impl));
std::cout << pizza1->name() << std::endl;
std::cout << pizza2->name() << std::endl;
}
In terms of where to put such code, have an overloaded operator<< is feasible.
I feel that the "pushing the decorator beyond it's intent" really needs emphasis here.
Would you really build a serious application whose functioning depends on parsing
"Mocha, Whip, Mocha"
and formulating
"Whip, Double Mocha"
Conceptually you are inferring semantics from an interface that is not published with that intent. The result will be very brittle, minor changes in implementations of decorators: "Yummy super mocha special" would break the parser. Adding new decorators would require unknown levels of change.

Initializing struct, using an array

I have a couple of array's:
const string a_strs[] = {"cr=1", "ag=2", "gnd=U", "prl=12", "av=123", "sz=345", "rc=6", "pc=12345"};
const string b_strs[] = {"cr=2", "sz=345", "ag=10", "gnd=M", "prl=11", "rc=6", "cp=34", "cv=54", "av=654", "ct=77", "pc=12345"};
which i then need to parse out for '=' and then put the values in the struct. (the rc key maps to the fc key in the struct), which is in the form of:
struct predict_cache_key {
pck() :
av_id(0),
sz_id(0),
cr_id(0),
cp_id(0),
cv_id(0),
ct_id(0),
fc(0),
gnd(0),
ag(0),
pc(0),
prl_id(0)
{ }
int av_id;
int sz_id;
int cr_id;
int cp_id;
int cv_id;
int ct_id;
int fc;
char gnd;
int ag;
int pc;
long prl_id;
};
The problem I am encountering is that the array's are not in sequence or in the same sequence as the struct fields. So, I need to check each and then come up with a scheme to put the same into the struct.
Any help in using C or C++ to solve the above?
Probably I didn't get it correctly, but obvious solutions is to split each array element into key and value and then write lo-o-ong if-else-if-else ... sequence like
if (!strcmp(key, "cr"))
my_struct.cr = value;
else if (!strcmp(key, "ag"))
my_struct.ag = value;
...
You can automate the creation of such sequence with the help of C preprocessor, e.g.
#define PROC_KEY_VALUE_PAIR(A) else if (!strcmp(key,#A)) my_struct.##A = value
Because of leading else you write the code this way:
if (0);
PROC_KEY_VALUE_PAIR(cr);
PROC_KEY_VALUE_PAIR(ag);
...
The only problem that some of you struct fields have _id sufffix - for them you'd need to create a bit different macro that will paste _id suffix
This shouldn't be too hard. Your first problem is that you don't have a fixed sized array, so you'd have to pass the size of the array, or what I'd prefer you make the arrays NULL-terminated, e.g.
const string a_strs[] = {"cr=1", "ag=2", "gnd=U", NULL};
Then I would write a (private) helper function that parse the string:
bool
parse_string(const string &str, char *buffer, size_t b_size, int *num)
{
char *ptr;
strncpy(buffer, str.c_str(), b_size);
buffer[b_size - 1] = 0;
/* find the '=' */
ptr = strchr(buffer, '=');
if (!ptr) return false;
*ptr = '\0';
ptr++;
*num = atoi(ptr);
return true;
}
then you can do what qrdl has suggested.
in a simple for loop:
for (const string *cur_str = array; *cur_str; cur_str++)
{
char key[128];
int value = 0;
if (!parse_string(*cur_string, key, sizeof(key), &value)
continue;
/* and here what qrdl suggested */
if (!strcmp(key, "cr")) cr_id = value;
else if ...
}
EDIT: you should probably use long instead of int and atol instead of atoi, because your prl_id is of the type long. Second if there could be wrong formated numbers after the '=', you should use strtol, which can catch errors.
I've written some little code that allows you to initialize fields, without having to worry too much about whether your fields are going out of order with the initialization.
Here is how you use it in your own code:
/* clients using the above classes derive from lookable_fields */
struct predict_cache_key : private lookable_fields<predict_cache_key> {
predict_cache_key(std::vector<std::string> const& vec) {
for(std::vector<std::string>::const_iterator it = vec.begin();
it != vec.end(); ++it) {
std::size_t i = it->find('=');
set_member(it->substr(0, i), it->substr(i + 1));
}
}
long get_prl() const {
return prl_id;
}
private:
/* ... and define the members that can be looked up. i've only
* implemented int, char and long for this answer. */
BEGIN_FIELDS(predict_cache_key)
FIELD(av_id);
FIELD(sz_id);
FIELD(gnd);
FIELD(prl_id);
END_FIELDS()
int av_id;
int sz_id;
char gnd;
long prl_id;
/* ... */
};
int main() {
std::string const a[] = { "av_id=10", "sz_id=10", "gnd=c",
"prl_id=1192" };
predict_cache_key haha(std::vector<std::string>(a, a + 4));
}
The framework is below
template<typename T>
struct entry {
enum type { tchar, tint, tlong } type_name;
/* default ctor, so we can std::map it */
entry() { }
template<typename R>
entry(R (T::*ptr)) {
set_ptr(ptr);
}
void set_ptr(char (T::*ptr)) {
type_name = tchar;
charp = ptr;
};
void set_ptr(int (T::*ptr)) {
type_name = tint;
intp = ptr;
};
void set_ptr(long (T::*ptr)) {
type_name = tlong;
longp = ptr;
};
union {
char (T::*charp);
int (T::*intp);
long (T::*longp);
};
};
#define BEGIN_FIELDS(CLASS) \
friend struct lookable_fields<CLASS>; \
private: \
static void init_fields_() { \
typedef CLASS parent_class;
#define FIELD(X) \
lookable_fields<parent_class>::entry_map[#X].set_ptr(&parent_class::X)
#define END_FIELDS() \
}
template<typename Derived>
struct lookable_fields {
protected:
lookable_fields() {
(void) &initializer; /* instantiate the object */
}
void set_member(std::string const& member, std::string const& value) {
typename entry_map_t::iterator it = entry_map.find(member);
if(it == entry_map.end()) {
std::ostringstream os;
os << "member '" << member << "' not found";
throw std::invalid_argument(os.str());
}
Derived * derived = static_cast<Derived*>(this);
std::istringstream ss(value);
switch(it->second.type_name) {
case entry_t::tchar: {
/* convert to char */
ss >> (derived->*it->second.charp);
break;
}
case entry_t::tint: {
/* convert to int */
ss >> (derived->*it->second.intp);
break;
}
case entry_t::tlong: {
/* convert to long */
ss >> (derived->*it->second.longp);
break;
}
}
}
typedef entry<Derived> entry_t;
typedef std::map<std::string, entry_t> entry_map_t;
static entry_map_t entry_map;
private:
struct init_helper {
init_helper() {
Derived::init_fields_();
}
};
/* will call the derived class's static init function */
static init_helper initializer;
};
template<typename T>
std::map< std::string, entry<T> > lookable_fields<T>::entry_map;
template<typename T>
typename lookable_fields<T>::init_helper lookable_fields<T>::initializer;
It works using the lesser known data-member-pointers, which you can take from a class using the syntax &classname::member.
Indeed, like many answered, there is a need to separate the parsing problem from the object construction problem. The Factory pattern is suited well for that.
The Boost.Spirit library also solves the parse->function problem in a very elegant way (uses EBNF notation).
I always like to separate the 'business logic' from the framework code.
You can achieve this by start writing "what you want to do" in a very convenient way and work to "how do you do it" from there.
const CMemberSetter<predict_cache_key>* setters[] =
#define SETTER( tag, type, member ) new TSetter<predict_cache_key,type>( #tag, &predict_cache_key::##member )
{ SETTER( "av", int, av_id )
, SETTER( "sz", int, sz_id )
, SETTER( "cr", int, cr_id )
, SETTER( "cp", int, cp_id )
, SETTER( "cv", int, cv_id )
, SETTER( "ct", int, ct_id )
, SETTER( "fc", int, fc )
, SETTER( "gnd", char, gnd )
, SETTER( "ag", int, ag )
, SETTER( "pc", int, pc )
, SETTER( "prl", long, prl_id )
};
PCKFactory<predict_cache_key> factory ( setters );
predict_cache_key a = factory.factor( a_strs );
predict_cache_key b = factory.factor( b_strs );
And the framework to achieve this:
// conversion from key=value pair to "set the value of a member"
// this class merely recognises a key and extracts the value part of the key=value string
//
template< typename BaseClass >
struct CMemberSetter {
const std::string key;
CMemberSetter( const string& aKey ): key( aKey ){}
bool try_set_value( BaseClass& p, const string& key_value ) const {
if( key_value.find( key ) == 0 ) {
size_t value_pos = key_value.find( "=" ) + 1;
action( p, key_value.substr( value_pos ) );
return true;
}
else return false;
}
virtual void action( BaseClass& p, const string& value ) const = 0;
};
// implementation of the action method
//
template< typename BaseClass, typename T >
struct TSetter : public CMemberSetter<BaseClass> {
typedef T BaseClass::*TMember;
TMember member;
TSetter( const string& aKey, const TMember t ): CMemberSetter( aKey ), member(t){}
virtual void action( BaseClass& p, const std::string& valuestring ) const {
// get value
T value ();
stringstream ( valuestring ) >> value;
(p.*member) = value;
}
};
template< typename BaseClass >
struct PCKFactory {
std::vector<const CMemberSetter<BaseClass>*> aSetters;
template< size_t N >
PCKFactory( const CMemberSetter<BaseClass>* (&setters)[N] )
: aSetters( setters, setters+N ) {}
template< size_t N >
BaseClass factor( const string (&key_value_pairs) [N] ) const {
BaseClass pck;
// process each key=value pair
for( const string* pair = key_value_pairs; pair != key_value_pairs + _countof( key_value_pairs); ++pair )
{
std::vector<const CMemberSetter<BaseClass>*>::const_iterator itSetter = aSetters.begin();
while( itSetter != aSetters.end() ) { // optimalization possible
if( (*itSetter)->try_set_value( pck, *pair ) )
break;
++itSetter;
}
}
return pck;
}
};
The problem is you dont have the metainformation to refer to the struct elements at run time (Something like structVar.$ElementName = ..., where $ElementName is not the element name but a (char?)variable containing the element name which should be used).
My solution would be to add this metainformation.
This should be an array with the offset of the elements in the struct.
Quick-n-Dirty solution: you add an array with the strings, the resulting code should look like this:
const char * wordlist[] = {"pc","gnd","ag","prl_id","fc"};
const int offsets[] = { offsetof(mystruct, pc), offsetof(mystruct, gnd), offsetof(mystruct, ag), offsetof(mystruct, prl_id), offsetof(mystruct, fc)};
const int sizes[] = { sizeof(mystruct.pc), sizeof(mystruct.gnd), sizeof(mystruct.ag), sizeof(mystruct.prl_id), sizeof(mystruct.fc)}
to enter something you would then something like this:
index = 0;
while (strcmp(wordlist[index], key) && index < 5)
index++;
if (index <5)
memcpy(&mystructvar + offsets[index], &value, sizes[index]);
else
fprintf(stderr, "Key not valid\n");
This loop for the inserts can get costly if you have bigger structures, but C doenst allow array indexing with strings. But the computer science found a solution for this problem: perfect hashes.
So it would afterwards look like this:
hash=calc_perf_hash(key);
memcpy(&mystruct + offsets[hash], &value, sizes[hash]);
But how to obtain these perfect hash functions (I called it calc_perf_hash)?
There exist algorithms for it where you just stuff your keywords in, and the functions comes out, and luckily someone even programmed them: look for the "gperf" tool/package in your faviourite OS/distribution.
There you would just input the 6 element names and he outputs you the ready to use C code for a perfect hash function (in generates per default a function "hash" which returnes the hash, and an "in_word_set" function which decides if a given key is in the word list).
Because the hash is in different order, you have of course to initilize the offsetof and size arrays in the order of the hashes.
Another problem you have (and which the other answers doesnt take into account) is the type conversion. The others make an assignment, I have (not better) memcopy.
Here I would suggest you change the sizes array into another array:
const char * modifier[]={"%i","%c", ...
Where each string describes the sscanf modifier to read it in. This way you can replace the assignment/copy by
sscanf(valueString, modifier[hash], &mystructVar + offsets(hash));
Cf course you can vary here, by including the "element=" into the string or similar. So you can put the complete string into value and dont have to preprocess it, I think this depends strongly on the rest of you parse routine.
Were I to do this in straight C, I wouldn't use the mother of all if's. Instead, I would do something like this:
typedef struct {
const char *fieldName;
int structOffset;
int fieldSize;
} t_fieldDef;
typedef struct {
int fieldCount;
t_fieldDef *defs;
} t_structLayout;
t_memberDef *GetFieldDefByName(const char *name, t_structLayout *layout)
{
t_fieldDef *defs = layout->defs;
int count = layout->fieldCount;
for (int i=0; i < count; i++) {
if (strcmp(name, defs->fieldName) == 0)
return defs;
defs++;
}
return NULL;
}
/* meta-circular usage */
static t_fieldDef metaFieldDefs[] = {
{ "fieldName", offsetof(t_fieldDef, fieldName), sizeof(const char *) },
{ "structOffset", offsetof(t_fieldDef, structOffset), sizeof(int) },
{ "fieldSize", offsetof(t_fieldDef, fieldSize), sizeof(int) }
};
static t_structLayout metaFieldDefLayout =
{ sizeof(metaFieldDefs) / sizeof(t_fieldDef), metaFieldDefs };
This lets you look up the field by name at runtime with a compact collection of the struct layout. This is fairly easy to maintain, but I don't like the sizeof(mumble) in the actual usage code - that requires that all struct definitions get labeled with comments saying, "don't effing change the types or content without changing them in the t_fieldDef array for this structure". There also needs to be NULL checking.
I'd also prefer that the lookup be either binary search or hash, but this is probably good enough for most cases. If I were to do hash, I'd put a pointer to a NULL hashtable into the t_structLayout and on first search, build the hash.
tried your idea and got an
error: ISO C++ forbids declaration of ‘map’ with no type
in linux ubuntu eclipse cdt.
I wish to notify that one should include <map> in the "*.h" file
in order to use your code without this error message.
#include <map>
// a framework
template<typename T>
struct entry {
enum type { tchar, tint, tlong } type_name;
/* default ctor, so we can std::map it */
entry() { }
template<typename R>
entry(R (T::*ptr)) {
etc' etc'......