C++ Keep tracks of changes inside a class - c++

Imagine a class representing a mail :
class mail {
string subject;
string content;
date receivedDate;
};
Now what I want to achieve is to know if my mail data is set, and once they're set, which ones where changed. I could go with a combination of std::optional and a std::map like this :
class Mail {
std::optional<string> subject;
std::optional<string> content;
std::optional<date> receivedDate;
enum EField { Subject, Content, ReceivedDate };
typedef std::map<EField, bool> ChangedMap;
ChangedMap changedFields;
public:
Mail(optional<string> subject, ... ) {
// initialize map with fields... hard coded
}
bool HasSubject() const { return subject; }
string GetSubject() const { return subject.get(); }
void SetSubject(const std::string& newSubject) {
subject = newSubject;
changedFields[Subject] = true;
}
void RemoveSubject() {
changedFields[Subject] = HasSubject();
subject.reset();
}
bool IsSubjectChanged() const {
return changedFields[Subject];
}
};
But I really think I am missing something crucial here. Would you see any better way to do it, preferably with less memory usage and no hardcoded values ?
I thought about about inheriting from std::optional but I don't see it as a good thing too.
Thanks

Let's generalize this problem: given a type T, I want a wrapper tracked<T> that keeps track of the history of reads/writes at run-time.
I would approach this problem by using std::tuple and metaprogramming. Firstly, let's define mail in terms of an std::tuple:
class mail
{
private:
std::tuple<string, string, date> _data;
public:
// `variant_type` could be automatically computed from the
// tuple type.
using variant_type = std::variant<string, string, date>;
enum class index
{
subject = 0,
content = 1,
date = 2
};
template <index TIndex>
decltype(auto) access()
{
return std::get<static_cast<std::size_t>(TIndex)>(_data);
}
};
I would then create something like tracked<T> that keeps track of the operations executed on T:
template <typename T>
class tracked
{
private:
using index_type = typename T::index;
using variant_type = typename T::variant_type;
struct write_action
{
variant_type _before;
variant_type _after;
};
struct read_action
{
index_type _index;
};
T _data;
std::vector<std::variant<write_action, read_action>> _history;
public:
template <index TIndex>
const auto& read() const
{
_history.emplace_back(read_action{TIndex});
return _data.access<TIndex>();
}
template <index TIndex, typename T>
void write(T&& new_value) const
{
// Remember previous value.
variant_type _before{_data.access<TIndex>()};
_history.emplace_back(write_action{_before, new_value});
return _data.access<TIndex>() = std::forward<T>(new_value);
}
};
The code above is not completely correct, as you need constructors for the action types, exception handling, move semantics support, and much more. I hope you get the general idea though.

Related

Elegant way to select a template implementation based on a variable of data types

I have a templated column reader
class ColumnIterator {
public:
};
template <typename DT>
class TypedColumnIterator : public ColumnIterator {
using data_type = typename DT::data_type;
public:
bool HasNext() { ... }
data_type Next() { ... }
};
class INT {
public:
using data_type = int32_t;
};
class DOUBLE {
public:
using data_type = double;
};
template class TypedColumnIterator<INT>;
template class TypedColumnIterator<DOUBLE>;
Now I have a table with multiple columns of different types
class Table {
vector<?> types_; // I have question here
public:
unique_ptr<ColumnIterator> col(int index) {
// I have question here
}
};
I want to maintain a vector of data types for each column and return a corresponding TypedColumnIterator based on the data type.
Question 1: What is a good way to maintain the data types? These are very similar to enums. The reason I cannot use enum is as you see, I need to maintain the data_type in them. So I create a parent class DataType and then let INT and DOUBLE extends from it. Then I create a static instance as I really only need one static instance for these data types. Is there a more elegant way to do this?
class INT : public DataType {
public:
static INT INST;
}
Question 2 I need a switch in the col function to choose the appropriate implementation. This is what I am doing now:
template<typename T>
bool is_INT(T &inst) {
return is_same<INT, T>();
}
unique_ptr<ColumnIterator> col(int index) {
auto type = types_[index];
if (is_INT(type))
return unique_ptr<ColumnIterator>(new TypedColumnIterator<INT>());
...
}
I feel like this is a bit silly. What's a smart way to implement this?
Thanks!
With std::variant you could do it like this:
class ColumnIterator {
public:
};
template <typename DT>
class TypedColumnIterator : public ColumnIterator {
using data_type = typename DT::data_type;
public:
bool HasNext() { ... }
data_type Next() { ... }
};
template<class DT>
struct Column {
using data_type = DT;
static auto create_iterator() {
return std::make_unique<TypedColumIterator<data_type>>();
}
};
using columns = std::variant<Column<INT>, Column<DOUBLE>>; // can add as many as you want
class Table {
vector<columns> types_;
public:
unique_ptr<ColumnIterator> col(int index) {
// Applies that lambda to the currently stored type in types_[index]
// The lambda works for each possible type that can be stored in the variant.
return std::visit([](const auto& col) { return col.create_iterator(); }, types_[index];
}
};
If that is the best way to do what you want, I cannot say. But it's reasonably good.

How to avoid mistakes in functions with same type arguments

How can I avoid mistakes with passing parameters of same type to function?
Let's consider function reading some binary data:
std::vector<uint8_t> read(size_t offset, size_t amount);
It's so easy to mistake offset with amount(I did similar it many times).
I see solution to this:
struct Offset
{
explicit Offset(size_t value) : value{value}{}
size_t value;
};
struct Amount
{
explicit Amount(size_t value) : value{value}{}
size_t value;
};
std::vector<uint8_t> read(Offset offset, Amount amount);
Is there a better solution to avoid mistakes like that?
There are two approaches I can think of.
Tagged Types
This is essentially what you are suggesting in your question, but I would implement it generically.
template <typename Tag, typename T>
struct Tagged
{
explicit Tagged(const T& value) : value{value} { }
T value;
};
template <typename Tag, typename T>
Tagged<Tag, T> tag(const T& value)
{
return Tagged<Tag, T>{value};
}
struct OffsetTag
{ };
struct AmountTag
{ };
using Offset = Tagged<OffsetTag, std::size_t>;
using Amount = Tagged<AmountTag, std::size_t>;
std::vector<uint8_t> read(Offset offset, Amount amount);
This allows you to expand the same concept to other underlying data types.
Named Parameter Idiom
The Named Parameter Idiom is somewhat similar to the Options approach in #PaulBelanger's answer, but it can be used in place and doesn't allow the user to take the the curly-brace shortcut that brings you back to the same problem you had before. However, it will default-initialize all your parameters, so while you are protected from mixing up parameters, it can't force you to provide explicit values for all of them. For your example:
class ReadParams
{
public:
ReadParams() : m_offset{0}, m_amount{128}
{ }
ReadParams& offset(std::size_t offset)
{
m_offset = offset;
return *this;
}
// Could get rid of this getter if you can make the users
// of this class friends.
std::size_t offset() const { return m_offset; }
ReadParams& amount(std::size_t amount)
{
m_amount = amount;
return *this;
}
// Could get rid of this getter if you can make the users
// of this class friends.
std::size_t amount() const { return m_amount; }
private:
std::size_t m_offset;
std::size_t m_amount;
};
std::vector<uint8_t> read(const ReadParams& params);
int main()
{
read(ReadParams{}.offset(42).amount(2048)); // clear parameter names
// read(ReadParams{42, 2048}); // won't compile
read(ReadParams{}.offset(42)); // also possible, amount uses default value
}
You could implement the members of ReadParams as std::optionals and throw a runtime error if an uninitialized member is access; but you can no longer enforce at compile time that the user actually provides all parameters.
Another thing that you can do is to pass parameters in a structure. This also allows you to set sensible defaults for the values. This is especially useful when a constructor takes a large number of arguments. For example:
class FooService
{
public:
// parameters struct.
struct Options
{
ip::address listen_address = ip::address::any();
uint16_t port = 1337;
bool allow_insecure_requests = false;
std::string base_directory = "/var/foo/"
};
//constructor takes the options struct to pass values.
explicit FooService(FooServiceOptions options);
// ...
};
Which is then used like:
FooService::Options o;
o.port = 1338;
//all other values keep their defaults.
auto service = make_unique<FooService>(o);

Template a member variable

Consider the following two classes:
class LunchBox
{
public:
std::vector<Apple> m_apples;
};
and
class ClassRoom
{
public:
std::vector<Student> m_students;
};
The classes are alike in that they both contain a member variable vector of objects; however, they are unalike in that the vector's objects are different and the member variables have different names.
I would like to write a template that takes either LunchBox or ClassRoom as a template argument (or some other parameter) and an existing object of the same type (similar to a std::shared_ptr). The template would return an object that adds a getNthElement(int i); member function to improve accessing the methods. Usage would be like:
// lunchBox is a previously initialized LunchBox
// object with apples already pushed into m_apples
auto lunchBoxWithAccessor = MyTemplate<LunchBox>(lunchBox);
auto apple3 = lunchBoxWithAccessor.getNthElement(3);
I would like to do this without writing template specializations for each class (which likely would require specifying the member variable to operate on in some way). Preferably, I do not want to modify the LunchBox or ClassRoom classes. Is writing such a template possible?
You can minimize the amount of code that has to be written for each class -- it doesn't have to be a template specialization and it doesn't have to be an entire class.
class LunchBox
{
public:
std::vector<Apple> m_apples;
};
class ClassRoom
{
public:
std::vector<Student> m_students;
};
// you need one function per type, to provide the member name
auto& get_associated_vector( Student& s ) { return s.m_apples; }
auto& get_associated_vector( ClassRoom& r ) { return r.m_students; }
// and then the decorator is generic
template<typename T>
class accessor_decorator
{
T& peer;
public:
auto& getNthElement( int i ) { return get_associated_vector(peer).at(i); }
auto& takeRandomElement( int i ) { ... }
// many more ways to manipulate the associated vector
auto operator->() { return &peer; }
};
LunchBox lunchBox{};
accessor_decorator<LunchBox> lunchBoxWithAccessor{lunchBox};
auto apple3 = lunchBoxWithAccessor.getNthElement(3);
The simple helper function overload should ideally be in the same namespace as the type, to make argument-dependent lookup work (aka Koenig lookup).
It's also possible to specify the member at the point of construction, if you prefer to do that:
template<typename T, typename TMemberCollection>
struct accessor_decorator
{
// public to make aggregate initialization work
// can be private if constructor is written
T& peer;
TMemberCollection const member;
public:
auto& getNthElement( int i ) { return (peer.*member).at(i); }
auto& takeRandomElement( int i ) { ... }
// many more ways to manipulate the associated vector
auto operator->() { return &peer; }
};
template<typename T, typename TMemberCollection>
auto make_accessor_decorator(T& object, TMemberCollection T::*member)
-> accessor_decorator<T, decltype(member)>
{
return { object, member };
}
LunchBox lunchBox{};
auto lunchBoxWithAccessor = make_accessor_decorator(lunchBox, &LunchBox::m_apples);
auto apple3 = lunchBoxWithAccessor.getNthElement(3);
A simple way to do this is define a trait struct that has specializations with just the information that makes each case different. Then you have a template class that uses this traits type:
// Declare traits type. There is no definition though. Only specializations.
template <typename>
struct AccessorTraits;
// Specialize traits type for LunchBox.
template <>
struct AccessorTraits<LunchBox>
{
typedef Apple &reference_type;
static reference_type getNthElement(LunchBox &box, std::size_t i)
{
return box.m_apples[i];
}
};
// Specialize traits type for ClassRoom.
template <>
struct AccessorTraits<ClassRoom>
{
typedef Student &reference_type;
static reference_type getNthElement(ClassRoom &box, std::size_t i)
{
return box.m_students[i];
}
};
// Template accessor; uses traits for types and implementation.
template <typename T>
class Accessor
{
public:
Accessor(T &pv) : v(pv) { }
typename AccessorTraits<T>::reference_type getNthElement(std::size_t i) const
{
return AccessorTraits<T>::getNthElement(v, i);
}
// Consider instead:
typename AccessorTraits<T>::reference_type operator[](std::size_t i) const
{
return AccessorTraits<T>::getNthElement(v, i);
}
private:
T &v;
};
A few notes:
In this case, the implementation would technically be shorter without a traits type; with only specializations of Accessor for each type. However, the traits pattern is a good thing to learn as you now have a way to statically reflect on LunchBox and ClassRoom in other contexts. Decoupling these pieces can be useful.
It would be more idiomatic C++ to use operator[] instead of getNthElement for Accessor. Then you can directly index the accessor objects.
AccessorTraits really isn't a good name for the traits type, but I'm having trouble coming up with anything better. It's not the traits of the accessors, but the traits of the other two relevant classes -- but what concept even relates those two classes? (Perhaps SchoolRelatedContainerTraits? Seems a bit wordy...)
You said:
I would like to do this without writing template specializations for each class
I am not sure why that is a constraint. What is not clear is what else are you not allowed to use.
If you are allowed to use couple of function overloads, you can get what you want.
std::vector<Apple> const& getObjects(LunchBox const& l)
{
return l.m_apples;
}
std::vector<Student> const& getObjects(ClassRoom const& c)
{
return c.m_students;
}
You can write generic code that works with both LaunchBox and ClassRoom without writing any other specializations. However, writing function overloads is a form of specialization.
Another option will be to update LaunchBox and ClassRoom with
class LunchBox
{
public:
std::vector<Apple> m_apples;
using ContainedType = Apple;
};
class ClassRoom
{
public:
std::vector<Student> m_students;
using ContainedType = Apple;
};
and then, take advantage of the fact that
LaunchBox b;
std::vector<Apple>* ptr = reinterpret_cast<std::vector<Apple>*>(&b);
is a legal construct. Then, the following class will work fine.
template <typename Container>
struct GetElementFunctor
{
using ContainedType = typename Container::ContainedType;
GetElementFunctor(Container const& c) : c_(c) {}
ContainedType const& getNthElement(std::size_t n) const
{
return reinterpret_cast<std::vector<ContainedType> const*>(&c_)->operator[](n);
}
Container const& c_;
};
and you can use it as:
LunchBox b;
b.m_apples.push_back({});
auto f = GetElementFunctor<LunchBox>(b);
auto item = f.getNthElement(0);
I did a test case sample using a few basic classes:
class Apple {
public:
std::string color_;
};
class Student {
public:
std::string name_;
};
class LunchBox {
public:
std::vector<Apple> container_;
};
class ClassRoom {
public:
std::vector<Student> container_;
};
However for the template function that I wrote I did however have to change the name of the containers in each class to match for this to work as this is my template function:
template<class T>
auto accessor(T obj, unsigned idx) {
return obj.container_[idx];
}
And this is what my main looks like:
int main() {
LunchBox lunchBox;
Apple green, red, yellow;
green.color_ = std::string( "Green" );
red.color_ = std::string( "Red" );
yellow.color_ = std::string( "Yellow" );
lunchBox.container_.push_back(green);
lunchBox.container_.push_back(red);
lunchBox.container_.push_back(yellow);
ClassRoom classRoom;
Student s1, s2, s3;
s1.name_ = std::string("John");
s2.name_ = std::string("Sara");
s3.name_ = std::string("Mike");
classRoom.container_.push_back(s1);
classRoom.container_.push_back(s2);
classRoom.container_.push_back(s3);
for (unsigned u = 0; u < 3; u++) {
auto somethingUsefull = accessor(lunchBox, u);
std::cout << somethingUsefull.color_ << std::endl;
auto somethingElseUsefull = accessor(classRoom, u);
std::cout << somethingElseUsefull.name_ << std::endl;
}
return 0;
}
I'm not sure if there is a work around to have a different variable name from each different class this function can use; but if there is I haven't figured it out as of yet. I can continue to work on this to see if I can improve it; but this is what I have come up with so far.

Better solution to data storage and passing

I'm trying to find a more elegant solution for some code I'm working on at the moment. I have data that needs to be stored then moved around, but I don't really want to take up any more space than I need to for the data that is stored.
I have 2 solutions, but neither seem very nice.
Using inheritance and a tag
enum class data_type{
first, second, third
};
class data_base{
public:
virtual data_type type() const noexcept = 0;
};
using data_ptr = data_base*;
class first_data: public data_base{
public:
data_type type() const noexcept{return data_type::first;}
// hold the first data type
};
// ...
Then you pass around a data_ptr and cast it to the appropriate type.
I really don't like this approach because it requires upwards casting and using bare pointers.
Using a union and storing all data types
enum class data_type{
first, second, third
};
class data{
public:
data(data_type type_): type(type_){}
data_type type;
union{
// first, second and third data types stored
};
};
But I don't like this approach because then you start wasting a lot of memory when you have a large data type that may get passed around.
This data will then be passed onto a function that will parse it into a greater expression. Something like this:
class expression{/* ... */};
class expr_type0: public expression{/* ... */};
// every expression type
using expr_ptr = expression*;
// remember to 'delete'
expr_ptr get_expression(){
data_ptr dat = get_data();
// interpret data
// may call 'get_data()' many times
expr_ptr expr = new /* expr_type[0-n] */
delete dat;
return expr;
}
and the problem arrises again, but it doesn't matter in this case because the expr_ptr doesn't need to be reinterpreted and will have a simple virtual function call.
What is a more elegant method of tagging and passing around the data to another function?
It's difficult to envisage exactly what you're looking for without more information. But if I wanted some framework that allowed me to store and retrieve data in some structured way, in as-yet-unknown storage devices this is the kind of way I'd be thinking.
This may not be the answer you're looking for, but I think there'll be concepts here that will inspire you in the right direction.
#include <iostream>
#include <tuple>
#include <boost/variant.hpp>
#include <map>
// define some concepts
// bigfoo is a class that's expensive to copy - so lets give it a shared-handle idiom
struct bigfoo {
struct impl {
impl(std::string data) : _data(std::move(data)) {}
void write(std::ostream& os) const {
os << "I am a big object. Don't copy me: " << _data;
}
private:
std::string _data;
};
bigfoo(std::string data) : _impl { std::make_shared<impl>(std::move(data)) } {};
friend std::ostream& operator<<(std::ostream&os, const bigfoo& bf) {
bf._impl->write(os);
return os;
}
private:
std::shared_ptr<impl> _impl;
};
// all the data types our framework handles
using abstract_data_type = boost::variant<int, std::string, double, bigfoo>;
// defines the general properties of a data table store concept
template<class...Columns>
struct table_definition
{
using row_type = std::tuple<Columns...>;
};
// the concept of being able to store some type of table data on some kind of storage medium
template<class IoDevice, class TableDefinition>
struct table_implementation
{
using io_device_type = IoDevice;
using row_writer_type = typename io_device_type::row_writer_type;
template<class...Args> table_implementation(Args&...args)
: _io_device(std::forward<Args>(args)...)
{}
template<class...Args>
void add_row(Args&&...args) {
auto row_instance = _io_device.open_row();
set_row_args(row_instance,
std::make_tuple(std::forward<Args>(args)...),
std::index_sequence_for<Args...>());
row_instance.commit();
}
private:
template<class Tuple, size_t...Is>
void set_row_args(row_writer_type& row_writer, const Tuple& args, std::index_sequence<Is...>)
{
using expand = int[];
expand x { 0, (row_writer.set_value(Is, std::get<Is>(args)), 0)... };
(void)x; // mark expand as unused;
}
private:
io_device_type _io_device;
};
// model the concepts into a concrete specialisation
// this is a 'data store' implementation which simply stores data to stdout in a structured way
struct std_out_io
{
struct row_writer_type
{
void set_value(size_t column, abstract_data_type value) {
// roll on c++17 with it's much-anticipated try_emplace...
auto ifind = _values.find(column);
if (ifind == end(_values)) {
ifind = _values.emplace(column, std::move(value)).first;
}
else {
ifind->second = std::move(value);
}
}
void commit()
{
std::cout << "{" << std::endl;
auto sep = "\t";
for (auto& item : _values) {
std::cout << sep << item.first << "=" << item.second;
sep = ",\n\t";
}
std::cout << "\n}";
}
private:
std::map<size_t, abstract_data_type> _values; // some value mapped by ascending column number
};
row_writer_type open_row() {
return row_writer_type();
}
};
// this is a model of a 'data table' concept
using my_table = table_definition<int, std::string, double, bigfoo>;
// here is a test
auto main() -> int
{
auto data_store = table_implementation<std_out_io, my_table>( /* std_out_io has default constructor */);
data_store.add_row(1, "hello", 6.6, bigfoo("lots and lots of data"));
return 0;
}
expected output:
{
0=1,
1=hello,
2=6.6,
3=I am a big object. Don't copy me: lots and lots of data
}

Dynamically define a function return type

I have a Message class that is able to pack its payload to binary and unpack it back. Like:
PayloadA p;
msg->Unpack(&p);
where PayloadA is a class.
The problem is that I have a bunch of payloads, so I need giant if or switch statement:
if (msg->PayloadType() == kPayloadTypeA)
{
PayloadA p;
msg->Unpack(&p); // void Unpack(IPayload *);
// do something with payload ...
}
else if ...
I want to write a helper function that unpacks payloads. But what would be the type of this function? Something like:
PayloadType UnpackPayload(IMessage *msg) { ... }
where PayloadType is a typedef of a proper payload class. I know it is impossible but I looking for solutions like this. Any ideas?
Thanks.
I would split one level higher to avoid the problem entirely:
#include <map>
#include <functional>
...
std::map<int, std::function<void()> _actions;
...
// In some init section
_actions[kPayloadA] = [](IMessage* msg) {
PayloadA p;
msg->Unpack(&p);
// do something with payload ...
};
// repeat for all payloads
...
// decoding function
DecodeMsg(IMessage* msg) {
_actions[id](msg);
}
To further reduce the code size, try to make Unpack a function template (possible easily only if it's not virtual, if it is you can try to add one level of indirection so that it isn't ;):
class Message {
template <class Payload>
Payload Unpack() { ... }
};
auto p = msg->Unpack<PayloadA>();
// do something with payload ...
EDIT
Now let's see how we can avoid writing the long list of _actions[kPayloadN]. This is highly non trivial.
First you need a helper to run code during the static initialization (i.e. before main):
template <class T>
class Registrable
{
struct Registrar
{
Registrar()
{
T::Init();
}
};
static Registrar R;
template <Registrar& r>
struct Force{ };
static Force<R> F; // Required to force all compilers to instantiate R
// it won't be done without this
};
template <class T>
typename Registrable<T>::Registrar Registrable<T>::R;
Now we need to define our actual registration logic:
typedef map<int, function<void()> PayloadActionsT;
inline PayloadActionsT& GetActions() // you may move this to a CPP
{
static PayloadActionsT all;
return all;
}
Then we factor in the parsing code:
template <class Payload>
struct BasePayload : Registrable<BasePayload>
{
static void Init()
{
GetActions()[Payload::Id] = [](IMessage* msg) {
auto p = msg->Unpack<Payload>();
p.Action();
}
}
};
Then we define all the payloads one by one
struct PayloadA : BasePayload<PayloadA>
{
static const int Id = /* something unique */;
void Action()
{ /* what to do with this payload */ }
}
Finally we parse the incoming messages:
void DecodeMessage(IMessage* msg)
{
static const auto& actions = GetActions();
actions[msg->GetPayloadType]();
}
How about a Factory Method that creates a payload according to the type, combined with a payload constructor for each payload type, taking a message as a parameter?
There's no avoiding the switch (or some similar construct), but at least it's straightforward and the construction code is separate from the switch.
Example:
class PayloadA : public Payload
{
public:
PayloadA(const &Message m) {...} // unpacks from m
};
class PayloadB : public Payload
{
public:
PayloadB(const &Message m) {...} // as above
};
Payload * UnpackFromMessage(const Message &m)
{
switch (m.PayloadType) :
case TypeA : return new PayloadA(m);
case TypeB : return new PayloadB(m);
... etc...
}
I seen this solved with unions. The first member of the union is the type of packet contained.
Examples here: What is a union?
An important question is how the payloads differ, and how they are the same. A system whereby you produce objects of a type determined by the payload, then interact with them via a virtual interface that is common to all types of payload, is reasonable in some cases.
Another option assuming you have a finite and fixed list of types of payload, returning a boost::variant is relatively easy. Then to process it, call apply_visitor with a functor that accepts every type in the variant.
If you only want to handle one type of payload differently, a "call and run the lambda if and only if the type matches T" function isn't that hard to write this way.
So you can get syntax like this:
struct State;
struct HandlePayload
{
typedef void return_type;
State* s;
HandlePayload(State* s_):s(s_) {}
void operator()( int const& payload ) const {
// handle int here
}
void operator()( std::shared_ptr<bob> const& payload ) const {
// handle bob ptrs here
}
template<typename T>
void operator()( T const& payload ) const {
// other types, maybe ignore them
}
}
which is cute and all, but you'll note it is quite indirect. However, you'll also note that you can write template code with a generic type T above to handle the payload, and use stuff like traits classes for some situations, or explicit specialization for others.
If you expect the payload to be one particular kind, and only want to do some special work in that case, writing a single-type handler on a boost::variant is easy.
template<typename T, typename Func>
struct Helper {
typedef bool return_type;
Func f;
Helper(Func f_):f(f_) {}
bool operator()(T const& t) {f(t); return true; }
template<typename U>
bool operator()(U const& u) { return false; }
};
template<typename T, typename Variant, typename Func>
bool ApplyFunc( Variant const& v, Func f )
{
return boost::apply_visitor( Helper<T, Func>(f), v );
}
which will call f on a variant v but only on the type T in the Variant, returning true iff the type is matched.
Using this, you can do stuff like:
boost::variant<int, double> v = 1.0;
boost::variant<int, double> v2 = int(1);
ApplyFunc<double>( v, [&](double d) { std::cout << "Double is " << d << "\n"; } );
ApplyFunc<double>( v2, [&](double d) { std::cout << "code is not run\n"; } );
ApplyFunc<int>( v2, [&](int i) { std::cout << "code is run\n"; } );
or some such variant.
One good solution is a common base class + all payloads inheriting from that class:
class PayLoadBase {
virtual char get_ch(int i) const=0;
virtual int num_chs() const=0;
};
And then the unpack would look like this:
class Unpacker {
public:
PayLoadBase &Unpack(IMessage *msg) {
switch(msg->PayLoadType()) {
case A: a = *msg; return a;
case B: b = *msg; return b;
...
}
}
private:
PayLoadA a;
PayLoadB b;
PayLoadC c;
};
You can make the function return a void *. A void pointer can be cast to any other type.