1

I've created the following helper function:

template<typename K1, typename V1, typename K2, typename V2>
[[nodiscard]] extern std::map<K2, V2> transform(const std::map<K1, V1> &map, std::function<std::pair<K2, V2>(K1, V1)> &&function) {
    std::map<K2, V2> transformedMap{};
    for (auto const &[k, v] : map) {
        auto pair = function(k, v);
        transformedMap.insert_or_assign(pair->first, pair->second);
    }
    return std::move(transformedMap);
}

Using it requires that I explicitly declare all types:

auto mapped = transform<std::string, std::any, std::string, std::string>(map, [](const std::string &key, const std::any &value) {
    return std::make_pair(key, std::any_cast<std::string>(value));
});

Is there any way for the compiler to deduce the types so that I can write:

auto mapped = transform(map, [](const std::string &key, const std::any &value) {
    return std::make_pair(key, std::any_cast<std::string>(value));
});
Quentamia
  • 3,244
  • 3
  • 33
  • 42
  • 1
    Template deduction can't be done with `std::function` using lambdas (see also https://stackoverflow.com/a/9998673/1678770). However you don't really _need_ `std::function` here at all; you could just take a deduce `Fn` type template argument that satisfies the [`std::is_invocable`](https://en.cppreference.com/w/cpp/types/is_invocable) trait with SFINAE (C++17) or the [`std::invocable`](https://en.cppreference.com/w/cpp/concepts/invocable) concept with constraitns (C++20) – Human-Compiler Jun 25 '21 at 16:49

1 Answers1

4

Template deduction can't be done with std::function using lambdas (see also C++11 does not deduce type when std::function or lambda functions are involved).

However you don't really need std::function here at all; you could just take function as a deduced template type-template argument, and then you can constrain it, and use auto to deduce the converted return type.

You just need to determine what K2 and V2 are based on the result of the function -- the result of which can be detected with the trait std::invoke_result_t. How you determine what K2 and V2 are is up to you -- but it can be quite easy if you choose to assume that function will always return some form of std::pair object, since you can just access the first_type and second_type from it.

Realistically, you can make this part as complex as you want; for example, you could use std::tuple_element_t or something if you wanted to support any abstract pair/tuple-like type. For simplicity I will only demonstrate the former though.

If you have access to this can be solved with the std::invocable concept:

#include <concepts>    // for std::invocable
#include <type_traits> // for std::invoke_result_t

template<typename K, typename V, typename Fn>
[[nodiscard]] 
auto transform(const std::map<K, V> &map, Fn&& function)
  requires(std::invocable<Fn,K,V>)
 {
    using pair_type = std::invoke_result_t<Fn,K,V>;
    using first_type = typename pair_type::first_type;
    using second_type = typename pair_type::second_type;
    
    auto transformedMap = std::map<first_type, second_type>{};
    for (auto const &[k, v] : map) {
        auto pair = function(k, v);
        transformedMap.insert_or_assign(pair.first, pair.second);
    }
    return transformedMap;
}

Live Example

If you don't have access to C++20, then the solution would be to use SFINAE with std::is_invocable:

#include <type_traits> // std::enable_if, std::is_invocable

template<typename K, typename V, typename Fn,
         typename = std::enable_if_t<std::is_invocable_v<Fn,K,V>>>
[[nodiscard]] 
... the rest is the same ...

Live Example


Note: Don't std::move on return values -- this is a pessimization that will prevent optimizations like copy-elision.

Human-Compiler
  • 11,022
  • 1
  • 32
  • 59