2012-09-23 66 views
4

我想写我自己的代表系统作为替代boost :: functions,因为后者做了很多堆分配,我认为是有问题的。
我写了这个作为替换(简化,实际的东西使用池式内存和安置新的,但,这是很简单的重现错误):
为什么在这种情况下C++模板参数推演失败?

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

编译器错误时I g等对版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解决方案。

回答

5

参数扣除剥离参考。通过匹配A的函数指针签名,我们希望得到int &,但通过匹配实际参数,我们想要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<T>::type是“身份类型”的推荐成语,其唯一目的是从参数推演中删除模板参数,之前被称为identity<T>::typealias<T>,但标准库特征std::common_type起到这个作用就好了。)

+0

头似乎是一个C++ 11头,但我只写了我自己的IdentityType的东西,它工作正常。很好的答案,非常感谢! – rsp1984

+0

@RafaelSpring:是的,在C++之前11人曾经推出自己的身份包装。好东西。为了完整起见,在C++ 11中,有些人喜欢使用'template 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) {} 

虽然上面的例子只是演示使用只是一个模板参数手头上的问题,我敢肯定,这可与更多的模板参数为好。无论这是什么我都不知道也不在乎。

+0

请解释一下:为什么我不想通过像这样的委托接口来引用值? – rsp1984

+1

在大多数情况下,您不希望立即调用该函数,而是稍后再调用该函数。使用'T const&'这些参数很可能会导致它们消失。对于'T&'你可能确实想直接使用它们,但即便如此,它们仍然很容易出错。 –

相关问题