0

I have a piece of code for sending data to remote side, it is acting a little like picojson, for example:

server::value::object obj;
obj["cmd"] = server::value("test");
obj["url"] = server::value(url);
...
obj["code"] = server::value(std::to_string(code));

server::value v(obj);
client.send_to_server(v.process());

As long as there is something to send, there will be a block like this.

These lines are used in so many places in my project that I'm thinking of using a function or << operator to replace it, which should be able to handle variable arguments.

A << operator version would be like:

Data d << "cmd" << "test"
       << "url" << url
       << ... << ...
       << "code" << code;
client.send_to_server(d);

Is this a good idea doing so? How to implement it?

Thanks.

Deqing
  • 14,098
  • 15
  • 84
  • 131
  • I'll give you a hint: `Data& operator<<(Data& data, const std::string& str);` for strings. – Some programmer dude Aug 03 '13 at 11:07
  • 1
    I've asked the same question a few days ago; see if [this solution](http://stackoverflow.com/questions/17868718/variadic-template-operator) fits your need. I was planning the writing of a serializer too. (: – Rubens Aug 03 '13 at 11:07

2 Answers2

1

Designing an API like that is not a good idea, because it is extremely error-prone.

A seemingly similar idea worked fine with output streams for a simple reason: the data that you put into the stream for output is treated uniformly. Anything that you put into << as an argument becomes part of the output, apart from stream manipulators, which control how the output is to be presented.

Your API is different: odd items are treated differently from the even ones. Moreover, it is a mistake to send an odd total number of operands. If for some reason you forget to put a string code on one of the lines, all values would silently become codes on the subsequent lines. Such API is very fragile, so I would strongly recommend agains it.

I think an API that lets your users add items in pairs would work better. If your compiler is C++11 compliant, you could also use uniform initialization syntax:

Data d = {
    {"cmd", server::value("test")}
,   {"url", server::value(url)}
,   {"code", server::value(std::to_string(code))}
};
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
1

I would write it differently:

d << add_value("cmd", "test")
  << add_value("url", url)
  << add_value(..., ...)
  << add_value("code", code)
  ;

Why? It's clearer and allows better type control.

So you create a class, say __add_value_temp which holds both the name and the value, a function add_value that creates this class, and write a << operator

Data &operator<<(Data &d, const __add_value_temp &val){
  d.add(val.name,val.val);
  return d;
}

Better yet - instead of __add_value_temp you can use an std::pair, and use std::make_pair instead of add_value!

Data &operator<<(Data &d, const std::pair<std::string,std::string> &val){
  d.add(val.first,val.second);
  return d;
}

...
Data d;
d << std::make_pair("cmd", "test")
  << std::make_pair("url", url)
  << std::make_pair(..., ...)
  << std::make_pair("code", code)
  ;

(last note: might be smart to write the operator<< templated on the types of the pair, so that you can pass things by reference and generally maybe avoid unneeded copying)

rabensky
  • 2,864
  • 13
  • 18
  • BTW - writing the `;` one line after helps with copying / pasting various values, as well as allows easy commenting of lines (of the last line, for example). – rabensky Aug 03 '13 at 11:21