Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ recursive template resolution: flattening vectors of vectors elegantly

Consider the following (naive) piece of C++ code to transfer objects from a custom list type into a std::vector

template<class A> void transfer(std::vector<A>& target, const custom_list_type& source){
  for(const A& elem:source){
    target.push_back(elem);
  }
}

Now, imagine one had a std::vector of such custom lists and would want to flatten the structure, or a std::vector of such vectors. Naively, I would now go ahead and write functions of this type.

template<class A> void flatten_transfer(std::vector<A>& target, const std::vector<custom_list_type>& source){
  for(const auto& elem:source){
    flat_transfer(target,elem);
  }
}

template<class A> void flatten_transfer(std::vector<A>& target, const std::vector<std::vector<custom_list_type> >& source){
  for(const auto& elem:source){
    flat_transfer(target,elem);
  }
}

And so on and so forth. But I see that this is not very elegant, because I need a version of this function of every depth level. I would imagine there is a more elegant way to solve this using some template magic, but I'm not really knowledgeable enough to come up with a solution that is genuinely nicer.

What would be the "recommended" way to abstract away the vector-depth-level using templates, such that only a single instance of flatten_transfer would need to be written?

like image 485
carsten Avatar asked Apr 16 '19 10:04

carsten


1 Answers

Assuming that I correctly understand the problem, the following function template well works for this purpose in C++17 and over. This function instantiates target.push_back(elem) if and only if A is same with B. Otherwise, this goes to the next depth:

Live DEMO

template<
    class A,
    class B,
    template <class...> class Container,
    class... extras>
void flat_transfer(std::vector<A>& target, const Container<B, extras...>& source)
{
    for(const auto& elem : source)
    {
        if constexpr(std::is_same<A, B>::value){
            target.push_back(elem);
        }
        else{
            flat_transfer(target, elem);
        }
    }
}

This is an usage example:

std::vector<int> v{1,2,3};
std::set<std::vector<std::deque<int>>, std::greater<>> vv{{{4,5}, {6,7}, {8,9}}, {{10,11,12}, {13,14}, {15}}};

flat_transfer(v, vv);

// prints "1 2 3 10 11 12 13 14 15 4 5 6 7 8 9" 
for(const auto& i : v){
    std::cout << i << " ";
}
like image 56
Hiroki Avatar answered Nov 18 '22 23:11

Hiroki