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!
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With