rabindra
rabindra

Reputation: 87

Automatically deducing element type of pair container from the function template argument

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

Answers (2)

WhiZTiM
WhiZTiM

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);

Demo

Upvotes: 3

yuri kilochek
yuri kilochek

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

Related Questions