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

2

10

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!

Psychiatry answered 12/6, 2018 at 22:11 Comment(0)
S
10

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

Saxen answered 12/6, 2018 at 22:18 Comment(8)
This doesn't compile for me because the deduced type for T1 and T2 are constEnglish
"You may remove them from list of template args (but that will require auto return type from c++11 or c++14)". auto is not required, it is just more convenient (as least the C++14 one).Lemma
@Lemma c++14 is not needed, yes (that's why I write c++11 or c++14 and left comment where to write return type for c++11). But I don't know way to do that in c++98 (there might be one, but compatibility with c++98 is usually not an issue for me so I never really thought of way to fix this under c++98)Saxen
By using verbose std::pair<std::vector<typename remove_const<typename T::value_type::first_type>::type>, std::vector<typename remove_const<typename T::value_type::second_type>::type>> (by also rewriting remove_const) twice (in return type, and in body).Lemma
@Lemma ah, of course, for some reason I thought I can't use T in return type, updatedSaxen
This is what I want to use, but these templates are driving me insane. I've never used std::remove_const (after including <type_traits> I don't seem to have std::remove_const_t). When using the c++11 solution, the compiler can't seem to convert the return values from the remove_const back to const: no known conversion for argument 1 from ‘std::vector<std::remove_const<const MyClass>, std::allocator<std::remove_const<const MyClass> > >’ to ‘const VecType& {aka const std::vector<MyClass>&}’Psychiatry
std::remove_const_t is c++14, right, so you need to use typename std::remove_const<T::value_type>::type. std::remove_const is (unrelated) type itselfSaxen
@Saxen thank you! That ::type was the last hint I needed. It works now. That c++14 auto return type is nice though, I hope my team migrates ahead soon.Psychiatry
C
-1
template<class T>
    struct zipper {
    using first_t = remove_cv_t<typename T::value_type::first_type>;
    using second_t = typename T::value_type::second_type;

    template <class container>
    static std::pair<std::vector<first_t>, std::vector<second_t>> unzip(const container& zipped)
    {
        auto unzipped = std::make_pair(std::vector<first_t>(), std::vector<second_t>());
        for (auto& one_two : zipped)
        {
            unzipped.first.emplace_back(one_two.first);
            unzipped.second.emplace_back(one_two.second);
        }
        return unzipped;
    }
};


template<class container>
auto unzip(const container& cont)  {
    return zipper<container>::unzip(cont);
}
Carious answered 12/6, 2018 at 22:17 Comment(4)
std::map doesn't get std::pair as one template argumentSaxen
you asked for a method that accepts an std::container of type std::pair<T,U> the code I posted works for an std::vector<std::pair<T,U>>Carious
are you asking for a single method that can handle both cases?Carious
it's not me who asking. (and I think yes, single method is what is asked (but I can be wrong, ofc)Saxen

© 2022 - 2024 — McMap. All rights reserved.