Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a c++ template that works for both a map and vector of pair>

I'd like to write a template function that iterates over a container of std::pair and returns a templated value with both types in the pair. I've gotten this to work for std::map as follows:

template <typename T1, typename T2>
std::pair<std::vector<T1>, std::vector<T2>> unzip(const std::map<T1,T2>& zipped)
{
    auto unzipped = std::make_pair(std::vector<T1>(), std::vector<T2>());
    for (auto& one_two : zipped)
    {
        unzipped.first.push_back(one_two.first);
        unzipped.second.push_back(one_two.second);
    }
    return unzipped;
}

This works fine, but it restricts the container to be std::map. What I'd like to accomplish is to have this also work for something like std::vector<std::pair<T1,T2>>, since the iteration over both containers works the same way.

I've tried to make the container a template by changing the template arguments:

template <typename T1, typename T2, template<typename ... Types> class Container>
std::pair<std::vector<T1>, std::vector<T2>> unzip(const Container<T1,T2>& zipped)
{
//do stuff and return
}

but in this case, the deduction fails if not using std::map because std::vector depends on only a single type. Then I tried getting more creative, but the compiler just complained even more:

template <typename PairContainerType>
std::pair<std::vector<typename PairContainerType::value_type::first_type>,
          std::vector<typename PairContainerType::value_type::second_type>>
unzip(const PairContainerType& zipped)
{
typedef typename PairContainerType::value_type::first_type T1;
typedef typename PairContainerType::value_type::second_type T2;
//do the same stuff and return
}

I think what I'm trying to do should be possible, but I'm at a loss. I'm using c++11 if that matters, though if what I want is available in future versions I'm still interested in those solutions. Thank you.


Update: Thanks to RiaD I got the following to work using c++11:

template <typename PairContainerType,
          typename T1 = typename std::remove_const<typename PairContainerType::value_type::first_type>::type,
          typename T2 = typename std::remove_const<typename PairContainerType::value_type::second_type>::type>
std::pair<std::vector<T1>, std::vector<T2>> unzip(const PairContainerType& zipped)
{
//do stuff and return
}

Note that it's slightly different than the accepted answer, since it uses std::remove_const instead of std::remove_const_t and needs to have ::type added at the end.

RiaD also points out that the template types T1 and T2 can be overridden by whoever is making the call. As suggested by Jarod42, this can be mitigated with some extra typing, which leaves me in the final c++11 solution:

template <typename PairContainerType>
std::pair<std::vector<typename std::remove_const<typename PairContainerType::value_type::first_type>::type>, 
          std::vector<typename std::remove_const<typename PairContainerType::value_type::second_type>::type>>
unzip(const PairContainerType& zipped)
{
    auto unzipped = std::make_pair(
        std::vector<typename std::remove_const<typename PairContainerType::value_type::first_type>::type>(), 
        std::vector<typename std::remove_const<typename PairContainerType::value_type::second_type>::type>());

    for (const auto& one_two : zipped)
    {
        unzipped.first.push_back(one_two.first);
        unzipped.second.push_back(one_two.second);
    }
    return unzipped;
}

Overall, c++14 and c++17 are looking pretty appealing. I could have saved myself time with the auto return feature!

like image 530
jonthalpy Avatar asked Jun 12 '18 22:06

jonthalpy


1 Answers

Usually with c++ you just allow any type and let it fail if it's not correct one. To get T1 and T2 you may get value_type of collection (exists in both std::map and std::vector<std::pair<>> and others e.g std::set<std::pair<>>)

template <typename T, typename T1 = std::remove_const_t<typename T::value_type::first_type>, typename T2 = std::remove_const_t<typename T ::value_type::second_type>>
std::pair<std::vector<T1>, std::vector<T2>> unzip(const T& zipped)
{
    auto unzipped = std::make_pair(std::vector<T1>(), std::vector<T2>());
    for (auto& one_two : zipped)
    {
        unzipped.first.push_back(one_two.first);
        unzipped.second.push_back(one_two.second);
    }
    return unzipped;
}

It has (slight) downside that somebody may force T1 and T2 as they fit. You may remove them from list of template args.

template <typename T>
auto /* or return type (but it will require copy-pasting) before c++14*/ 
unzip(const T& zipped)
{
    using T1 = std::remove_const_t<typename T::value_type::first_type>; //may need to remove const
    using T2 = std::remove_const_t<typename T::value_type::second_type>; 
    // impl
}

Removing const is needed because value_type of std::map is std::pair<const K, V>. it may be done without std::remove_const_t if you need older standard support e.g. for c++11 you need typename std::remove_const<>::type

like image 183
RiaD Avatar answered Oct 02 '22 14:10

RiaD