【问题标题】:templated function argument in C++14C++14 中的模板化函数参数
【发布时间】:2017-07-29 03:00:48
【问题描述】:

由于模板类型推导的问题,这段代码无法编译,甚至在 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
}

【问题讨论】:

  • 除非你要把merge_sorted重载到死,否则我只会为a_before_b的类型使用一个单独的模板参数,不需要std::function。
  • @Marc:我不确定我是否理解 - 请您详细说明一下,可能以答案的形式?
  • 注意,您可以在 merge_sorted 正文中重新使用 std::merge
  • 感谢大家的cmets和回答。你让我相信谓词模板参数是要走的路。现在我的代码编译了……我发现它仍然存在缺陷(在达到 b.end() 之后访问 *ib)……但这是一个完全不同的故事。

标签: c++ templates lambda c++14 std-function


【解决方案1】:

这里的问题是f 不是std::function。它是一些未命名的类类型,但不是std::function。当编译器进行模板参数推导时,它不进行任何转换,它按原样使用参数来推导它们的类型。这意味着它期望看到 std::function&lt;bool(const T, const T)&gt; 的地方看到 main()::&lt;lambda(double, double)&gt;,因为那是 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 或仿函数并不重要。

【讨论】:

  • 很好的解释,谢谢。我倾向于从fstd::function 的演员阵容。你会怎么做? std::bind 相关吗?
  • 当我们这样做的时候,为什么不去掉 std::vector&lt;T&gt; 并用迭代器重写它呢?
  • @JoachimWuttke:普通模板是一种更通用的解决方案,并且具有更高的内联潜力。
  • @Pixelchemist: "plain template" = Nathan 的解决方案 = Angew 的解决方案?
  • @JoachimWuttke std::function 也禁止不可复制的类型,可以用 lambda 捕获。
【解决方案2】:

您需要以某种方式使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 在 cmets 中指出的那样),首先对于a_before_b 的类型强制使用std::function 是完全没有必要的。更不用说它很容易带来性能损失(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)

【讨论】:

    【解决方案3】:
    1. 错误来自于编译器试图推断 T,它无法推断 Tstd::function 参数,该参数传递了一个 lambda。

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

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

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

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

      幸运的是,编译器足够聪明,可以内联 lambda,尽管它被“通过”std::function 传递到模板中,但我不会打赌。相反,如果我通过它自己的类型传递它,我实际上希望编译器内联一个 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() &amp;&amp; ib!=b.end();),这让我们在 ab 中留下元素。

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

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

    【讨论】:

      【解决方案4】:

      由于我无法发表评论:一般是@NathanOliver 所说的。 lambda expression 不能“强制转换”为 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?the old MSDN article here

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

          for (;;ia!=a.end() || ib!=b.end())
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-11-30
        • 2010-09-18
        • 1970-01-01
        • 2015-09-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-23
        • 1970-01-01
        相关资源
        最近更新 更多