goaran
goaran

Reputation: 495

Auto generating conditional expression from list/map

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

Answers (2)

Etienne Laurin
Etienne Laurin

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

Vittorio Romeo
Vittorio Romeo

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

live example on wandbox.org

Upvotes: 2

Related Questions