2012-09-23 63 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&'你可能確實想直接使用它們,但即便如此,它們仍然很容易出錯。 –

相關問題