可以使用 std 容器为函数模板推导类型参数吗?

Can type arguments be made deduceable for function templates using std container?

我发现此实现实现了函数式编程的一些常见功能,例如地图/减少: (我知道类似的东西显然会出现或部分出现在新的 C++ 版本中)

github link

部分代码:

template <typename T, typename U>
U foldLeft(const std::vector<T>& data,
           const U& initialValue,
           const std::function<U(U,T)>& foldFn) {
    typedef typename std::vector<T>::const_iterator Iterator;
    U accumulator = initialValue;
    Iterator end = data.cend();
    for (Iterator it = data.cbegin(); it != end; ++it) {
        accumulator = foldFn(accumulator, *it);
    }
    return accumulator;
}

template <typename T, typename U>
std::vector<U> map(const std::vector<T>& data, const std::function<U(T)> mapper) {
    std::vector<U> result;
    foldLeft<T, std::vector<U>&>(data, result, [mapper] (std::vector<U>& res, T value)  -> std::vector<U>& {
        res.push_back(mapper(value));
        return res;
    });
    return result;
}

用法示例:

std::vector<int> biggerInts = map<int,int>(test, [] (int num) { return num + 10; });

类型参数 T,U 必须完全限定才能编译,如示例所示,例如地图< int, int >( ... )。 如链接页面所述,此实现适用于 C++11。

现在是否可以使用较新的 C++ 版本(甚至 11)来使用这种不那么冗长的方式,即自动推导类型 U、T? 我用谷歌搜索了一下,只发现 class 模板显然有一些改进,而不是函数模板,C++17 中的参数推导。 但是由于我只以相当基本的方式使用过模板,所以我想知道是否存在我不知道的东西可以改进这个实现的冗长程度。

您可以将 map 签名重写为:

template <typename T, typename M, typename U = decltype(std::declval<M>()(T{}))>
std::vector<U> map(const std::vector<T>& data, const M mapper)

然后 T 将被推断为 value_type 向量的项目。

M 是任何可调用对象。

当调用 T{}.

时,

U 被推断为 return 类型的 M() 仿函数

低于

std::vector<int> biggerInts = map(test, [] (int num) { return num + 10; });
                              ^^^^ empty template arguments list

工作正常。

Live demo

更通用的模板使模板参数推导更容易。

一个原则:使用std::function作为模板函数的参数通常是错误的。 std::function 是一种类型擦除,用于需要将某些未知的可调用对象存储为特定类型时使用。但是模板已经具备处理任意可调用类型的能力。因此,如果我们只使用通用的 typename FuncT 模板参数,则可以直接将其推导为原始函数指针、lambda 或另一个 class 和 operator()

我们也可以更通用并接受任何输入容器,而不仅仅是 vector,然后从中确定 T,如果直接需要的话。

所以对于 C++11,我会重写这些:

// C++20 is adding std::remove_cvref, but it's trivial to implement:
template <typename T>
using remove_cvref_t =
    typename std::remove_cv<typename std::remove_reference<T>::type>::type;

template <typename Container, typename U, typename FuncT>
remove_cvref_t<U> foldLeft(
           const Container& data,
           U&& initialValue,
           const FuncT& foldFn) {
    remove_cvref_t<U> accumulator = std::forward<U>(initialValue);
    for (const auto& elem : data) {
        accumulator = foldFn(std::move(accumulator), elem);
    }
    return accumulator;
}

template <typename Container, typename FuncT>
auto map(const Container& data, const FuncT& mapper)
    -> std::vector<remove_cvref_t<decltype(mapper(*std::begin(data)))>>
{
    using T = remove_cvref_t<decltype(*std::begin(data))>;
    using ResultT = std::vector<remove_cvref_t<decltype(mapper(std::declval<const T&>()))>>;
    ResultT result;
    foldLeft(data, std::ref(result), [&mapper] (ResultT &res, const T& value)  -> ResultT& {
        res.push_back(mapper(value));
        return res;
    });
    return result;
}

参见working program on coliru

旧的 map 有一件不幸的事:它可能会在每次迭代时复制结果向量。 accumulator = foldFn(accumulator, *it);中的=是自赋值,可能什么都不做,也可能分配新内存,复制内容,然后释放旧内存,更新容器。因此,在这种情况下,我将 foldLeftU 更改为 std::reference_wrapper。在这种情况下,= 仍将 "rebind" 包装到同一对象,但这至少会很快。

在 C++14 及更高版本中,您可以通过使用通用 lambda 来避免在 map 中查找 T[&mapper] (std::vector<U>& res, const auto& value) ...