Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deduce template return type in C++

Currently I try to write a function retrieveKeys() which gives me the keys of a std::map and stores it in some std::container. The function shall be generic in two ways:

  • Accept std::map and std::unordered_map as parameter type.
  • Return the keys in a user defined container, e. g. std::vector or std::deque (the container has to supports a push_back() method).

Currently the use of this function works as follows:

std::unordered_map<int, int> testMap;
std::map<int, int> testMap2;

std::vector<int> keys1 = retrieveKeys<std::vector>(testMap);    
std::deque<int> keys2 = retrieveKeys<std::deque>(testMap);
std::vector<int> keys3 = retrieveKeys<std::vector>(testMap2);
std::deque<int> keys4 = retrieveKeys<std::deque>(testMap2);

With the following function:

template<template<typename...> typename KeyContainer, typename... KeyContainer_Rest,
         template<typename...> typename MapContainer, typename K, typename V, typename... MapContainer_Rest>
inline KeyContainer<K, KeyContainer_Rest...> retrieveKeys(const MapContainer<K, V, MapContainer_Rest...>& map)
{
    KeyContainer<K, KeyContainer_Rest...> keys;

    for (const auto& m : map)
    {
        keys.push_back(m.first);
    }

    return keys;
}

It would be nice if I would not have to write the return type explicitly. But when I try something like

std::vector<int> keys1_ = retrieveKeys(testMap);
/*
error: no matching function for call to 'retrieveKeys'
std::vector<int> keys1_ = retrieveKeys(testMap);
                          ^~~~~~~~~~~~
*/

I get the mentioned error when compiling with clang 3.6 (C++17).

So my question is: Is it possible to rewrite the function so that the return type can be reduced by the compiler?

Here again the complete code for easy copying:

#include <deque>
#include <vector>
#include <unordered_map>
#include <map>

template<template<typename...> typename KeyContainer, typename... KeyContainer_Rest,
         template<typename...> typename MapContainer, typename K, typename V, typename... MapContainer_Rest>
inline KeyContainer<K, KeyContainer_Rest...> retrieveKeys(const MapContainer<K, V, MapContainer_Rest...>& map)
{
    KeyContainer<K, KeyContainer_Rest...> keys;

    for (const auto& m : map)
    {
        keys.push_back(m.first);
    }

    return keys;
}

int main() 
{
    std::unordered_map<int, int> testMap;
    std::map<int, int> testMap2;

    std::vector<int> keys1 = retrieveKeys<std::vector>(testMap);    
    std::deque<int> keys2 = retrieveKeys<std::deque>(testMap);
    std::vector<int> keys3 = retrieveKeys<std::vector>(testMap2);
    std::deque<int> keys4 = retrieveKeys<std::deque>(testMap2);

    //std::vector<int> keys1_ = retrieveKeys(testMap);
    /*
    error: no matching function for call to 'retrieveKeys'
    std::vector<int> keys1_ = retrieveKeys(testMap);
                              ^~~~~~~~~~~~
    */
}
like image 937
Milania Avatar asked Feb 10 '23 00:02

Milania


2 Answers

template <typename K, typename M>
struct ReturnTypeDeducer
{
    const M& map;

    ReturnTypeDeducer(const M& m) : map(m) {}

    template <template <typename...> typename KeyContainer, typename... KeyContainer_Rest>
    operator KeyContainer<K, KeyContainer_Rest...>() &&
    {
        KeyContainer<K, KeyContainer_Rest...> keys;
        for (const auto& m : map)
        {
            keys.push_back(m.first);
        }
        return keys;
    }
};

template <template <typename...> typename MapContainer, typename K, typename V, typename... MapContainer_Rest>
inline ReturnTypeDeducer<K, MapContainer<K, V, MapContainer_Rest...>> retrieveKeys(const MapContainer<K, V, MapContainer_Rest...>& map)
{
    return map;
}

int main() 
{
    std::unordered_map<int, int> testMap;
    std::map<int, int> testMap2;

    std::vector<int> keys1 = retrieveKeys(testMap);    
    std::deque<int> keys2 = retrieveKeys(testMap);
    std::vector<int> keys3 = retrieveKeys(testMap2);
    std::deque<int> keys4 = retrieveKeys(testMap2);
}

DEMO

like image 138
Piotr Skotnicki Avatar answered Feb 16 '23 02:02

Piotr Skotnicki


No. There's no way for the compiler to tell what the return type should be because it has no information it can use to determine that (you can call retrieveKeys() outside of the context of immediately assigning it to a variable of the type you want).

However, you can reduce the code duplication by using auto:

auto keys1 = retrieveKeys<std::vector>(testMap);    
auto keys2 = retrieveKeys<std::deque>(testMap);
auto keys3 = retrieveKeys<std::vector>(testMap2);
auto keys4 = retrieveKeys<std::deque>(testMap2);
like image 45
Claudiu Avatar answered Feb 16 '23 03:02

Claudiu