Reputation: 495
I am working on a program, that has to initialize many different objects according to a list that defines which type each object is.
The code that does this task looks like this:
// name is one entry of the type list
// itemList is a std::vector where the new items are appended
if(name == "foo")
{
initItem<FooObject>(itemList);
}
else if(name == "bar")
{
initItem<BarObject>(itemList);
}
else if(name == "baz")
{
initItem<Object3>(itemList);
}
....
initItem(ItemList) allocates an object of type T and appends it to itemList.
At other place in the code there are similar conditional statements for the different object types. At the moment for each new object type added I have to add a new else if to all the conditional statements which is kind of annoying.
Is there a way to just define some kind of map somewhere that holds the assignment like
"foo", FooObject,
"bar", BarObject,
"baz", Object3,
and then template/auto-generate (maybe by preprocessor) the if-else statements so i don't have to setup them by hand every time?
Edit: Here is the whole method that contains the code snipset (there are many more else if() statements that all work according to the same principal.
bool Model::xml2Tree(const pugi::xml_node &xml_node, std::vector<TreeItem*> &parents)
{
bool all_ok = true;
bool sucess;
pugi::xml_node child_node = xml_node.first_child();
for (; child_node; child_node = child_node.next_sibling())
{
sucess = true;
bool ok = false;
std::string name = child_node.name();
if(name == "foo")
{
ok = initTreeItem<FooObject>(child_node, parents);
}
else if(name == "bar")
{
ok = initTreeItem<BarObject>(child_node, parents);
}
...
...
...
else
{
ok = false;
std::cout << "Unknown Element" << std::endl;
continue;
}
if(!sucess)
{
continue;
}
all_ok = all_ok && ok;
// recursiv
ok = xml2Tree(child_node, parents);
all_ok = all_ok && ok;
}
parents.pop_back();
return all_ok;
}
template<class T>
bool Model::initTreeItem(const pugi::xml_node &xml_node,
std::vector<TreeItem *> &parents)
{
bool ok = false;
T *pos = new T(parents.back());
parents.back()->appendChild(pos);
ok = pos->initFromXml(xml_node);
parents.push_back(pos);
return ok;
}
Upvotes: 2
Views: 88
Reputation: 7184
You can use higher-order macros (or x-macros) to generate code like that, for example:
#define each_item(item, sep) \
item("foo", FooObject) sep \
item("bar", BarObject) sep \
item("baz", Object3)
#define item_init(item_name, item_type) \
if (name == item_name) { \
initItem<item_type>(itemList); \
}
each_item(item_init, else)
Upvotes: 2
Reputation: 93324
Firstly, you can encode your mapping in the type system as follows:
template <typename T>
struct type_wrapper { using type = T; };
template <typename T>
inline constexpr type_wrapper<T> t{};
template <typename K, typename V>
struct pair
{
K _k;
V _v;
constexpr pair(K k, V v) : _k{k}, _v{v} { }
};
template <typename... Ts>
struct map : Ts...
{
constexpr map(Ts... xs) : Ts{xs}... { }
};
constexpr auto my_map = map{
pair{[]{ return "foo"; }, t<FooObject>},
pair{[]{ return "bar"; }, t<BarObject>},
pair{[]{ return "baz"; }, t<Object3>}
};
We're using lambdas as they're implicitly constexpr
in C++17, in order to simulate "constexpr
arguments". If you do not require this, you can create a constexpr
wrapper over a string literal and use that instead.
You can then go through the mapping with something like this:
template <typename... Pairs>
void for_kv_pairs(const std::string& name, map<Pairs...> m)
{
([&]<typename K, typename V>(const pair<K, V>& p)
{
if(name == p._k())
{
initItem<typename V::type>();
}
}(static_cast<const Pairs&>(m)), ...);
}
This is using a fold expression over the comma operator plus C++20 template syntax in lambdas. The latter can be replaced by providing an extra implementation function to retrieve K
and V
from pair
pre-C++20.
Usage:
template <typename X>
void initItem()
{
std::cout << typeid(X).name() << '\n';
}
struct FooObject { };
struct BarObject { };
struct Object3 { };
constexpr auto my_map = map{
pair{[]{ return "foo"; }, t<FooObject>},
pair{[]{ return "bar"; }, t<BarObject>},
pair{[]{ return "baz"; }, t<Object3>}
};
int main()
{
for_kv_pairs("bar", my_map);
}
Output:
9BarObject
Upvotes: 2