【问题标题】:Why is c++ template argument deduction failing in this case?为什么在这种情况下 C++ 模板参数推导失败?
【发布时间】:2012-09-15 07:24:48
【问题描述】:

我正在尝试编写自己的委托系统来替代 boost::functions,因为后者做了很多我认为有问题的堆分配。
我已经写了这个作为替代(简化,实际使用池化内存和放置新,但这很简单,可以重现错误):

template<class A, class B>
struct DelegateFunctor : public MyFunctor {
  DelegateFunctor(void (*fptr)(A, B), A arg1, B arg2) : fp(fptr), a1(arg1), a2(arg2) {}

  virtual void operator()() { fp(a1, a2); }

  void (*fp)(A, B);  // Stores the function pointer.
  const A a1; const B a2;  // Stores the arguments.
};

还有这个辅助函数:

template<class A, class B>
MyFunctor* makeFunctor(void (*f)(A,B), A arg1, B arg2) {
  return new DelegateFunctor<A,B>(f, arg1, arg2);
}

奇怪的事情发生在这里:

void bar1(int a, int b) {
  // do something
}

void bar2(int& a, const int& b) {
  // do domething
}

int main() {
  int a = 0;
  int b = 1;

  // A: Desired syntax and compiles.
  MyFunctor* df1 = makeFunctor(&bar1, 1, 2);

  // B: Desired syntax but does not compile:
  MyFunctor* df2 = makeFunctor(&bar2, a, b);

  // C: Not even this:
  MyFunctor* df3 = makeFunctor(&bar2, (int&)a, (const int&)b);

  // D: Compiles but I have to specify the whole damn thing:
  MyFunctor* df4 = makeFunctor<int&, const int&>(&bar2, a, b);
}

我得到的版本 C(B 类似)的编译器错误是:

error: no matching function for call to ‘makeFunctor(void (*)(int&, const int&), int&, const int&)’

这很奇怪,因为编译器在其错误消息中实际上正确推断了类型。

有什么方法可以让我编译 B 版吗? boost::bind 如何绕过这个限制?
我正在使用 GCC 4.2.1。请不要使用 C++11 解决方案。

【问题讨论】:

    标签: c++ templates boost delegates function-pointers


    【解决方案1】:

    论据推论去掉了引用。通过匹配A的函数指针签名,我们想要得到int &amp;,但是通过匹配实际参数,我们想要int,所以推导失败。

    一种解决方案是使第二种类型不推导,如下所示:

    #include <type_traits>
    
    template <typename R, typename A, typename B>
    R do_it(R (*func)(A, B),
            typename std::common_type<A>::type a,   // not deduced
            typename std::common_type<B>::type b)   // not deduced
    {
        return func(a, b);
    }
    

    现在AB 完全由函数指针的签名确定:

    int foo(int &, int const &);
    
    int main()
    {
        int a = 0, b = 0;
        return do_it(foo, a, b);  // deduces A = int &, B = int const &
    }
    

    (注意std::common_type&lt;T&gt;::type 是“身份类型”的推荐习惯用法,其唯一目的是从参数推导中删除模板参数。这以前被称为identity&lt;T&gt;::typealias&lt;T&gt;,但是标准库 trait std::common_type 可以很好地满足这个目的。)

    【讨论】:

    • 标头似乎是 C++11 标头,但我只是编写了自己的 IdentityType 东西,效果很好。很好的答案,非常感谢!
    • @RafaelSpring:是的,在 C++11 之前,人们习惯于推出自己的身份包装器。好东西。为了完整起见,在 C++11 中,有些人喜欢使用 template &lt;typename T&gt; using alias = T;,但这在我身上还没有成长。
    【解决方案2】:

    当使用值参数推导模板参数时,你只会得到一个值类型。也就是说,当您使用像

    这样的函数模板时
    template <typename T>
    void f(T) {
    }
    

    T 类型始终是非引用类型。现在,当您尝试传递函数指针和值时,如果函数不采用值类型,编译器将无法使推导的类型保持一致:

    template <typename T>
    void f(void (*)(T), T) {}
    
    void f0(int);
    void f1(int const&);
    
    int main() {
        f(&f0, 0); // OK
        f(&f1, 0); // ERROR
    }
    

    处理这个问题的一种方法是适当地重载相应的函数模板。如果您将以下函数添加到混合中,上述示例将再次起作用:

    template <typename T>
    void f(void (*)(T const&), T const&) {}
    

    显然,这很快就会成为维护的噩梦,并且可能不是您想要做的。另一种方法是为各个参数使用不同的模板参数:

    template <typename T, typename S>
    void f(void (*)(T), S) {}
    

    虽然这可行,但它的直接影响是您不一定匹配您真正想要匹配第二个参数的类型:即使您可能想要获取引用类型,它也会是一个值类型(个人而言,我怀疑你这样做,但这是一个不同的问题)。如果您不希望这种情况发生,您可以阻止编译器形式尝试推断某些参数的参数。例如:

    template <typename T>
    struct helper {
        typedef T type;
    };
    template <typename T>
    void f(void (*)(T), typename helper<T>::type) {}
    

    虽然上面的示例仅使用一个模板参数演示了手头的问题,但我确信这也适用于更多模板参数。这是否是 Boost 我既不知道也不关心的。

    【讨论】:

    • 请解释一下:为什么我不想通过这样的委托接口通过引用传递值?
    • 在大多数情况下,您不想立即调用该函数,而是稍后再调用。对这些参数使用T const&amp; 很可能会导致它们消失。对于T&amp;,您可能确实想直接使用它们,但即便如此,隐式绑定它们也很容易出错。
    猜你喜欢
    • 2017-05-20
    • 2012-02-07
    • 1970-01-01
    • 2020-11-29
    • 2013-04-30
    • 1970-01-01
    • 1970-01-01
    • 2018-09-08
    • 1970-01-01
    相关资源
    最近更新 更多