How to implement a template common for maps and sets?

Viewed 163

I'm loading various data from database, and have already implemented a template-method:

template <typename R>
bool
loadMap(map<const string, R> &store, const char *query) {
{
...
      for (auto row : rows(query)) {
          store.insert(make_pair(row[0], R(row[1], row[2], ....));
      }
}

The method sends the query to the DB-server and populates the store with the results. The first column becomes the key of the map, and the rest of the fields form the value structure. That works.

However, in a couple of cases, the queries returns just one column -- which is stored in a set (or unordered_set) -- without any values associated with it.

Currently those cases are handled by methods of their own, and I'd like to extend my loadMap-function to these other types of containers -- perhaps, renaming it to loadContainer.

How would this new loadContainer's declaration look, and how would it invoke the store.insert() with just the key, no values?

(I'm aware of the dodge of turning the set into a map with all values being empty, but I'd like to avoid that.)

Actually, I cannot even think of a way to handle map and unordered_map -- though the insert would be exactly the same, how would I declare such a function? The two map-types don't seem to have a common ancestor (nor "interface", to use a Java-term)...

2 Answers

You might have overloads

template <typename R>
bool loadContainer(std::map<string, R> &store, const char *query) {
{
    // ...
    for (auto row : rows(query)) {
        store.insert(std::make_pair(row[0], R(row[1], row[2] /*, ...*/));
    }
    // ...
}

bool loadContainer(std::set<string> &store, const char *query) {
{
    // ...
    for (auto row : rows(query)) {
        store.insert(row[0]);
    }
    // ...
}

Alternatively, to avoid overload, you might do (C++17)

template <typename Container>
bool loadContainer(Container& store, const char *query) {
{
    // ...
    for (auto row : rows(query)) {
        if constexpr (is_map<Container>::value) {
            using R = typename Container::mapped_type;
            store.insert(std::make_pair(row[0], R(row[1], row[2] /*, ...*/));
        } else {
            store.insert(row[0]);
        }
    }
    // ...
}

with appropriate traits as for example

template <typename T, typename Enabler = void>
struct is_map : std::false_type {};

template <typename T>
struct is_map<T, std::void_t<typename T::mapped_type>> : std::true_type {};

Having solved the problem of distinguishing between maps and sets, I was able to implement different insert-functions for different container-types.

My loadMap templatized function is simply calling the insert(store, key, value) -- and the correct insert implementation is picked at compile time depending on the type of the store. (For sets, the value is ignored.)

Related