【问题标题】:C++11 does not deduce type when std::function or lambda functions are involved当涉及 std::function 或 lambda 函数时,C++11 不会推断类型
【发布时间】:2012-04-17 09:50:58
【问题描述】:

当我定义这个函数时,

template<class A>
set<A> test(const set<A>& input) {
    return input;
}

我可以在代码的其他地方使用test(mySet) 调用它,而无需显式定义模板类型。但是,当我使用以下功能时:

template<class A>
set<A> filter(const set<A>& input,function<bool(A)> compare) {
    set<A> ret;
    for(auto it = input.begin(); it != input.end(); it++) {
        if(compare(*it)) {
            ret.insert(*it);
        }
    }
    return ret;
}

当我使用filter(mySet,[](int i) { return i%2==0; }); 调用此函数时 我收到以下错误:

错误:没有匹配的函数调用'filter(std::set&, main()::)'

但是,所有这些版本都可以工作:

std::function<bool(int)> func = [](int i) { return i%2 ==0; };
set<int> myNewSet = filter(mySet,func);

set<int> myNewSet = filter<int>(mySet,[](int i) { return i%2==0; });

set<int> myNewSet = filter(mySet,function<bool(int)>([](int i){return i%2==0;}));

为什么不直接创建std::function,直接把lambda函数放在表达式里面,c++11就猜不出模板类型?

编辑:

根据 cmets 中 Luc Danton 的建议,这是我之前使用的不需要显式传递模板的函数的替代方法。

template<class A,class CompareFunction>
set<A> filter(const set<A>& input,CompareFunction compare) {
    set<A> ret;
    for(auto it = input.begin(); it != input.end(); it++) {
        if(compare(*it)) {
            ret.insert(*it);
        }
    }
    return ret;
}

这可以通过set&lt;int&gt; result = filter(myIntSet,[](int i) { i % 2 == 0; }); 调用而不需要模板。

编译器甚至可以在某种程度上猜测返回类型,使用新的 decltype 关键字和使用新的函数返回类型语法。这是一个将集合转换为映射的示例,使用一个过滤函数和一个基于值生成键的函数:

template<class Value,class CompareType,class IndexType>
auto filter(const set<Value>& input,CompareType compare,IndexType index) -> map<decltype(index(*(input.begin()))),Value> {
    map<decltype(index(*(input.begin()))),Value> ret;
    for(auto it = input.begin(); it != input.end(); it++) {
        if(compare(*it)) {
            ret[index(*it)] = *it;
        }
    }
    return ret;
}

也可以不直接使用模板调用,如

map<string,int> s = filter(myIntSet,[](int i) { return i%2==0; },[](int i) { return toString(i); });

【问题讨论】:

  • 与您的问题无关,但您确实意识到您的filter 本质上等同于std::copy_if 的非通用版本,不是吗?
  • 啊,我不知道 std::copy_if,谢谢你指出这一点。但是,这是一组更大的 4 个函数的一部分,其中一个函数在过滤时转换 set => map,我看不到使用 copy_if 实现它并允许用户使用集合中的值生成键的方法。为了使用的一致性,我选择这样做。
  • 为了记录,如果你想接受一个仿函数,通常习惯用它作为一个通用的模板参数,即template&lt;typename A, typename Predicate&gt; set&lt;A&gt; filter(set&lt;A&gt; const&amp; input, Predicate compare);。正如您所看到的,std::function 无法记录传递的谓词应该具有匹配bool(A) 的签名;还有其他方法可以做到这一点。此外,使用std::function 作为函数参数还有其他个缺点。

标签: c++ templates lambda c++11


【解决方案1】:

问题在于 lambda 的性质。根据标准,它们是具有一组固定属性的函数对象,但它们不是一个函数。该标准确定 lambda 可以转换为具有确切参数类型的 std::function&lt;&gt;,如果它们没有状态,则使用函数指针。

但这并不意味着 lambda 是 std::function 也不是函数指针。它们是实现operator() 的独特类型。

另一方面,类型推导只会推导出确切的类型,没有转换(除了 const/volatile 限定)。因为 lambda 不是 std::function,编译器无法推断调用中的类型:filter(mySet,[](int i) { return i%2==0; }); 是任何 std::function&lt;&gt; 实例化。

与其他示例一样,在第一个示例中,您将 lambda 转换为函数类型,然后将其传递。编译器可以在那里推断出类型,如第三个示例中std::function 是相同类型的右值(临时)。

如果您将实例化类型int 提供给模板,第二个工作示例,演绎不会发挥作用,编译器将使用该类型,然后将 lambda 转换为适当的类型。

【讨论】:

  • 请注意,不是 lambda 执行到 std::function 的转换,而是 std::function 接受任何可调用的内容。
  • 类型推导也会做基本类型的“转换”来完成。
  • 可以通过禁用compare arg 的扣除来使其工作。我们可以用这个签名来做到这一点:template&lt;class A&gt; set&lt;A&gt; filter(const set&lt;A&gt;&amp; input ,typename NoOp&lt;function&lt;bool(A)&gt; &gt;::type compare ) 其中Noop 被定义为template &lt;typename T&gt; struct NoOp { typedef T type; };
  • ... 这允许 A 由第一个 arg 推断,然后按原样用于 compare arg。
  • @AaronMcDaid 这是一个不错的解决方案,但我不想在需要这个的时候引入 NoOp 结构。您是否碰巧知道这种NoOp 类型是否在现代C++ 标准中作为STL 的一部分提供?看起来很有用。
【解决方案2】:

忘记你的情况。因为这太复杂了,无法分析。

举个简单的例子:

 template<typename T>
 struct X 
 {
     X(T data) {}
 };

 template<typename T>
 void f(X<T> x) {}

现在打电话给f

 f(10); 

在这里,您可能会认为T 将被推导出为int 并且因此,上述函数调用应该可以工作。好吧,事实并非如此。为简单起见,假设有 另一个 构造函数,它采用 int 为:

 template<typename T>
 struct X 
 {
     X(T data) {}
     X(int data) {} //another constructor
 };

现在,当我写f(10) 时,应该将T 推导出为什么?好吧,T 可以任何类型。

请注意,可能还有许多其他此类情况。以这个专业为例:

 template<typename T>
 struct X<T*>         //specialized for pointers
 {
    X(int data) {}; 
 };

现在应该为调用f(10) 推断出什么T?现在似乎更难了。

因此它是不可演绎的上下文,这就解释了为什么您的代码不适用于std::function,这是一个相同的情况——只是表面上看起来很复杂。请注意,lambdas 不是std::function 类型——它们基本上是编译器生成的类的实例(即它们是std::function 不同的 类型的函子。

【讨论】:

    【解决方案3】:

    如果我们有:

    template <typename R, typename T>
    int myfunc(std::function<R(T)> lambda)
    {
      return lambda(2);
    }
    
    int r = myfunc([](int i) { return i + 1; });
    

    它不会编译。 但如果你之前声明:

    template <typename Func, typename Arg1>
    static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr) -> decltype((*func)(*arg1));
    
    template <typename Func>
    int myfunc(Func lambda)
    {
      return myfunc<int, decltype(getFuncType<Func, int>())>(lambda);
    }
    

    您可以毫无问题地使用 lambda 参数调用您的函数。

    这里有 2 段新代码。

    首先,我们有一个函数声明,它只对返回有用 基于给定模板的旧式函数指针类型 参数:

    template <typename Func, typename Arg1>
    static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr) -> decltype((*func)(*arg1)) {};
    

    其次,我们有一个函数,它接受一个模板参数来构建我们预期的调用“getFuncType”的 lambda 类型:

    template <typename Func>
    int myfunc(Func lambda)
    {
      return myfunc<int, decltype(getFuncType<Func, int>())>(lambda);
    }
    

    有了正确的模板参数,现在我们可以调用真正的'myfunc'了。 完整的代码将是:

    template <typename R, typename T>
    int myfunc(std::function<R(T)> lambda)
    {
      return lambda(2);
    }
    
    template <typename Func, typename Arg1>
    static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr) -> decltype((*func)(*arg1)) {};
    
    template <typename Func>
    int myfunc(Func lambda)
    {
      return myfunc<int, decltype(getFuncType<Func, int>())>(lambda);
    }
    
    int r = myfunc([](int i) { return i + 1; });
    

    您可以为“getFuncType”声明任何重载以匹配您的 lambda 参数。例如:

    template <typename Func, typename Arg1, typename Arg2>
    static auto getFuncType(Func* func = nullptr, Arg1* arg1 = nullptr, Arg2* arg2 = nullptr) -> decltype((*func)(*arg1, *arg2)) {};
    

    【讨论】:

    • 请注意,我是从 Boolinq 项目 link 学到的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-23
    相关资源
    最近更新 更多