Reputation: 87
I want to write a template function which takes a container of key-value pairs (e.g map<K,V>
or vector<pair<K,V>>
) and returns a container of container of keys. For example:
template<typename C, typename K, typename V>
vector<vector<K>> partition_keys(const C& input)
{
vector<vector<K>> result;
...
// do something
for (const auto &pair : input)
{
cout << pair.first << "," << pair.second << std::endl;
}
...
return result;
}
Here's how I would like to call it:
// map
map<string, string> product_categories;
partition_keys(product_categories); // doesn't work
partition_keys<map<string, string>, string, string>(product_categories); // have to do this
// vector
vector<pair<string, string>> movie_genres;
partition_keys(movie_genres); // doesn't work
partition_keys<vector<pair<string, string>>, string, string>(movie_genres); // have to do this
But, the compiler cannot deduce the template parameters K and V without explicitly specifying them. I want the function to work with any container having pair of any type; so I want to avoid writing separate template functions for map<K,V>
, list<pair<K,V>>
, vector<pair<K,V>>
, etc.
So, I had to modify the template function signature as follows to make it work the way I want:
template<typename C,
typename K = remove_const_t<C::value_type::first_type>,
typename V = C::value_type::second_type>
vector<vector<K>> partition_keys(const C& input);
Is there a better way to do this? Is it a good practice to deduce types of K
and V
based on value_type
of C
? Also, there is a possibility of caller explicitly passing invalid arguments for K
and V
.
Note also how I had remove constness of key type by calling remove_const_t
because for a map
, C::value_type::first_type
is a const
type and the standard doesn't allow creating a collection of a const
type.
Upvotes: 1
Views: 1614
Reputation: 21576
You are doing the right way, more specifically:
template<typename C,
typename Pair = typename C::value_type,
typename Key = std::remove_const_t<typename Pair::first_type>,
typename Value = typename Pair::first_type
>
vector<vector<Key>> partition_keys(const C& input)
is correct (Demo). However, if you need to use similar type decomposition for different template functions like:
....repeat above templated type decomposition....
vector<vector<Key>> sorted_keys(const C& input);
....repeat above templated type decomposition....
vector<vector<Key>> filtered_keys(const C& input);
It may be too much of typing to do. In that case, you can make a simple trait class to help you with that.
template<typename T>
struct PTraits{
using pair_type = typename T::value_type;
using key_type = std::remove_const_t<typename pair_type::first_type>;
using value_type = typename pair_type::second_type;
};
template<typename T>
using KeyTypper = typename PTraits<T>::key_type;
Then use as...
template<typename C, typename Key = KeyTypper<C>>
vector<vector<Key>> partition_keys(const C& input);
template<typename C, typename Key = KeyTypper<C>>
vector<vector<Key>> sorted_keys(const C& input);
template<typename C, typename Key = KeyTypper<C>>
vector<vector<Key>> filtered_keys(const C& input);
Upvotes: 3
Reputation: 13486
It is fine. If you don't like the clutter in template parameters, you can put the key type directly in the return type, possibly in trailing form:
template <typename C>
auto partition_keys(const C& input)
-> vector<vector<remove_const_t<typename C::value_type::first_type>>>;
or rely on return type deduction for normal functions and omit the return type entirely:
template <typename C>
auto partition_keys(const C& input)
{
vector<vector<remove_const_t<typename C::value_type::first_type>>> result;
//...
return result;
}
Upvotes: 1