C++14 中的模板化函数参数

templated function argument in C++14

此代码无法编译,甚至在 C++14 下也无法编译,因为模板类型推导存在问题。最不优雅的解决方法是什么?

#include <vector>
#include <functional>
#include <iostream>

template <class T>
std::vector<T> merge_sorted(
    const std::vector<T>& a, const std::vector<T>& b,
    std::function<bool(const T, const T)> a_before_b)
{
    std::vector<T> ret;
    auto ia=a.begin();
    auto ib=b.begin();
    for (;;ia!=a.end() || ib!=b.end())
        ret.push_back( a_before_b(*ia,*ib) ? *(ia++) : *(ib++) );
    return ret;
}

int main()
{
    std::vector<double> A { 1.1, 1.3, 1.8 };
    std::vector<double> B { 2.1, 2.2, 2.4, 2.7 };
    auto f = [](const double a, const double b) -> bool {
        return (a-(long)(a))<=(b-(long(b))); };
    std::vector<double> C = merge_sorted(A, B, f);
    for (double c: C)
        std::cout << c << std::endl;
    // expected outout: 1.1 2.1 2.2 1.3 2.4 2.7 1.8
}

这里是来自 g++ -std=c++14 main.cpp 的错误消息:

main.cpp: In function ‘int main()’:
main.cpp:23:49: error: no matching function for call to ‘merge_sorted(std::vector<double>&, std::vector<double>&, main()::<lambda(double, double)>&)’
     std::vector<double> C = merge_sorted(A, B, f);
                                                 ^
main.cpp:6:16: note: candidate: template<class T> std::vector<T> merge_sorted(const std::vector<T>&, const std::vector<T>&, std::function<bool(T, T)>)
 std::vector<T> merge_sorted(
                ^~~~~~~~~~~~
main.cpp:6:16: note:   template argument deduction/substitution failed:
main.cpp:23:49: note:   ‘main()::<lambda(double, double)>’ is not derived from ‘std::function<bool(T, T)>’
     std::vector<double> C = merge_sorted(A, B, f);

==

稍后编辑,仅作记录:这里有一个代码版本可以编译(感谢收到的答案)并且可以正确执行(对上述未经测试的代码进行了多次更正):

#include <vector>
#include <functional>
#include <iostream>

template <class T, class Pred>
std::vector<T> merge_sorted(const std::vector<T>& a, const std::vector<T>& b, Pred a_before_b)
{
    std::vector<T> ret;
    auto ia=a.begin();
    auto ib=b.begin();
    for (;ia!=a.end() && ib!=b.end();)
        ret.push_back( a_before_b(*ia,*ib) ? *(ia++) : *(ib++) );
    for (;ia!=a.end();)
        ret.push_back( *(ia++) );
    for (;ib!=b.end();)
        ret.push_back( *(ib++) );
    return ret;
}

int main()
{
    std::vector<double> A { 1.1, 1.3, 1.8 };
    std::vector<double> B { 2.1, 2.2, 2.4, 2.7 };
    auto f = [](const double a, const double b) -> bool {
        return (a-(long)(a))<=(b-(long(b))); };
    std::vector<double> C = merge_sorted(A, B, f);
    for (double c: C)
        std::cout << c << std::endl;
    // expected outout: 1.1 2.1 2.2 1.3 2.4 2.7 1.8
}

您需要以某种方式使 a_brefore_b 的类型成为非推导上下文。我通常为此介绍一个适当命名的助手:

template <class T>
struct NonDeduced
{
  using type = T;
};

template <class T>
std::vector<T> merge_sorted(
    const std::vector<T>& a, const std::vector<T>& b,
    typename NonDeduced<std::function<bool(const T, const T)>>>::type a_before_b)

当然(正如@Marc Glisse 在评论中指出的那样),完全没有必要首先强制使用 std::function 作为 a_before_b 类型。更不用说它很容易带来性能损失(std::function 在内部使用类型擦除和动态调度)。只需按照标准库所做的,并通过模板参数键入谓词:

template <class T, class Pred>
std::vector<T> merge_sorted(
    const std::vector<T>& a, const std::vector<T>& b,
    Pred a_before_b)

这里的问题是 f 不是 std::function。它是一些未命名的 class 类型,但它不是 std::function。当编译器进行模板参数推导时,它不会进行任何转换,它会按原样使用参数来推导它们的类型。这意味着它期望看到 std::function<bool(const T, const T)> 的地方它看到 main()::<lambda(double, double)> 因为那是 lambda 的类型并且因为这些类型不匹配推导失败。为了使推论成功,您需要让它们匹配。

在不更改函数签名的情况下,您必须将 f 转换为 std::function 才能使其正常工作。那看起来像

std::vector<double> C = merge_sorted(A, B, static_cast<std::function<bool(const double,const double)>>(f));

如果您不介意更改函数签名,那么我们可以使用

template <class T, class Func>
std::vector<T> merge_sorted(
    const std::vector<T>& a, const std::vector<T>& b,
    Func a_before_b)

现在,传递 std::function 或 lambda 或仿函数都没有关系。

因为我无法发表评论:一般来说@NathanOliver 所说的。 lambda expression 不能是 "cast" 到 std::function,因为它在内部是一种不同的结构。 当然,如果编译器可以推断(通过静态分析)它必须为 lambda 创建一个 std::function 对象,那就太好了。但这似乎不是 C++11/C++14 的一部分。

要解决这个问题,我发现最简单的方法是在模板中添加 typename

template <class T, typename F>
std::vector<T> merge_sorted(
    const std::vector<T>& a, const std::vector<T>& b,
    F& a_before_b)

当然你也可以使用class。请参阅问题 Use 'class' or 'typename' for template parameters? and the old MSDN article here

此外,请注意您在第 13 行中有错字。您的意思可能是:

    for (;;ia!=a.end() || ib!=b.end())
  1. 错误来自于编译器试图推导 T 而它无法推导 T 用于传递 lambda 的 std::function 参数。

  2. 标准为此类谓词使用普通模板参数是有充分理由的。

    2.1 谓词使用模板参数是最通用的。

    您可以传入 std::functionstd::bind、函数指针、lambda、仿函数...

    2.2 内联(如果可能)最有可能发生。

    幸运的是,尽管将 "through" 和 std::function 传递到模板中,编译器足够聪明以内联 lambda,但我不会打赌。相反,如果我通过它自己的类型传递它,我实际上希望编译器内联一个 lambda(如果合适的话)。

  3. 您的代码还有其他几个问题。

    3.1 for (;;ia!=a.end() || ib!=b.end())这里;设置错误

    3.2 即使 ; 设置正确,谓词也是错误的,因为 ia!=a.end() || ib!=b.end() 将保持循环 运行,即使 ia == a.end()ib == b.end() 为真。在循环中,两个迭代器都被取消引用以检查谓词,如果我们已经超过最后一个元素,这将导致我们进入未定义的行为领域。因此,循环条件必须是 for (;ia!=a.end() && ib!=b.end();),这使我们在 ab.

  4. 中留下元素

如果您追求性能和通用性,您可能想写以下内容:

template <class InIt, class OutIt, class Predicate>
auto merge_sorted(InIt first1, InIt last1, InIt first2, InIt last2, 
    OutIt dest, Predicate pred)
{
    // as long as we have elements left in BOTH ranges
    for (;first1 != last1 && first2 != last2; ++dest)
    {
        // check predicate which range offers the lowest value
        // and insert it
        if (pred(*first1, *first2)) *dest = *(first1++);
        else *dest = *(first2++);
    }
    // here either first1 == last1 or first2 == last2 is true
    // thus we can savely copy the "rest" of both ranges 
    // to dest since we only have elements in one of them left anyway
    std::copy(first1, last1, dest);
    std::copy(first2, last2, dest);
    return pred;
}