2

I would like to write a generic function that takes some map-like type, and returns all the keys in the map. I would also like the API of the function to be extremely simple, like this:

std::unordered_map<int, std::string> stringTable;
auto stringTableKeys = keys(stringTable); // should return a std::vector<int>

std::map<std::string, double> doubleTable;
auto doubleTableKeys = keys(doubleTable); // should return a std::vector<std::string>

I don't want it to be tied down to std::map or std::unordered_map, nor do I want to be tied down to specific key or value types, so I tried to write the following:

template <typename Key, typename Value, template <typename, typename...> typename Table>
std::vector<Key> keys(const Table<Key, Value>& table)
{
    std::vector<Key> keys;
    keys.reserve(table.size());
    for (const auto& [key, value] : table)
        keys.push_back(key);
    return keys;
}

However, if I want to use this version of the keys() function, I have to call it like this

auto stringTableKeys = keys<int, std::string, std::unordered_map>(stringTable);

How can I specify the template in the definition of keys() so that the caller does not have to specify the types?

  • If your function already relies on the assumption that the range-based `for` loop you've written works correctly, I'm not sure you lose that much more genericness by making use of `key_type`. E.g. just have `MapType` as a class template argument and return a `std::vector`. – Nathan Pierson Feb 20 '21 at 21:28
  • 2
    Am I missing something; your attempt seems to [work](https://godbolt.org/z/bexP5n)? – cigien Feb 20 '21 at 21:32

2 Answers2

0

Sorry for the confusion, it turns out my code actually does work. https://onlinegdb.com/By6hgZkMd

This is just a bug in Visual Studio 2019, I wasn't sure if it was going to work because it said it was a syntax error: enter image description here

  • Was this a compiler error when you built the code, or did you get an error in the IDE? – cigien Feb 20 '21 at 21:42
  • 1
    @cigien no compiler error, only an IDE error. – golgi apparatus Feb 20 '21 at 21:48
  • 3
    I don't see anything saying "syntax error" in your screenshot, only some [Intellisense](https://stackoverflow.com/questions/9732639/c-cli-errors-in-intellisense-compiles-fine/9742407#9742407) [shortcomings](https://stackoverflow.com/questions/31943634/visual-studio-2015-or-2017-shows-intellisense-errors-but-solution-compiles). Let your compiler have the final say, not Unintellinonsense. :) – JaMiT Feb 20 '21 at 21:48
0

The code you presented compiles. However, there are a few problems with it.

It will fail to compile if the input map type uses template parameters that are different from the default. This is because the keys signature does not deduce template parameters of Table other than the first two. For example, calling keys like this will fail:

std::map<int, std::string, std::greater<int>> stringTable;
auto stringTableKeys = keys(stringTable); // the deduced type of table argument
                                          // does not match that of stringTable

To fix this you could adjust keys signature as follows:

template <typename Key, typename... Args,
    template <typename, typename...> typename Table>
std::vector<Key> keys(const Table<Key, Args...>& table)
{
    std::vector<Key> keys;
    keys.reserve(table.size());
    for (const auto& [key, value] : table)
        keys.push_back(key);
    return keys;
}

Here's the code.

However, this will still break if the type of the argument does not match the expected template <typename, typename...> typename Table signature, where the first template parameter denotes the key type. To mitigate this, you could use the nested key_type or value_type types that are defined by all associative containers. For example:

template <typename Container>
std::vector<typename Container::key_type> keys(const Container& table)
{
    std::vector<typename Container::key_type> keys;
    keys.reserve(table.size());
    for (const auto& [key, value] : table)
        keys.push_back(key);
    return keys;
}

or:

template <typename Container>
std::vector<std::remove_const_t<
    typename Container::value_type::first_type>>
    keys(const Container& table)
{
    std::vector<std::remove_const_t<
        typename Container::value_type::first_type>> keys;
    keys.reserve(table.size());
    for (const auto& [key, value] : table)
        keys.push_back(key);
    return keys;
}

Note that Container::value_type for all associative containers is std::pair<const Key, Value>, and std::pair defines first_type and second_type nested typedefs to access its element types. We need to use std::remove_const_t to get rid of the const qualification of the key type.

The latter version of keys is especially useful as it will work not only with associative containers, but also with any sequence of std::pairs. See the code here.

Andrey Semashev
  • 10,046
  • 1
  • 17
  • 27