首页 > 解决方案 > 自动推断模板类型(带 lambda)

问题描述

我创建了以下辅助函数:

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

使用它需要我明确声明所有类型:

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

编译器有什么方法可以推断类型以便我可以编写:

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

标签: c++templates

解决方案


使用 lambda无法完成模板推导std::function(另请参阅C++11 在涉及 std::function 或 lambda 函数时不推导类型)。

但是,您根本不需要 std::function这里;您可以将function其作为推导的模板类型模板参数,然后您可以对其进行约束,并使用它auto来推导转换后的返回类型。

您只需要根据函数的结果来确定是什么K2V2是什么——其结果可以通过 trait 检测到std::invoke_result_t。如何确定什么K2V2是由您决定——但如果您选择假设它function总是会返回某种形式的std::pair对象,这将非常容易,因为您可以从中访问first_typesecond_type

实际上,您可以根据需要使这部分变得复杂;例如,std::tuple_element_t如果您想支持任何抽象对/类似元组的类型,您可以使用或其他东西。为简单起见,我将只演示前者。

如果您可以访问,则可以通过以下std::invocable概念解决:

#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

如果您无权访问 C++20,则解决方案是使用 SFINAE 和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


注意:不要std::move返回值——这是一种悲观,会阻止像复制省略这样的优化


推荐阅读