2013-05-28 26 views
3

我有以下模板類充當代理。它有一個名爲call的方法,該方法應該用於調用包裝對象上的方法。這有一個問題。類型扣除失敗,我不明白爲什麼。類型扣減失敗,並指向成員方法

Hudsucker::f需要std::string然後無論我通過std::stringconst引用它編譯器能夠調用正確的方法。

但是在Hudsucker::g的情況下需要const參考std::string在兩種情況下都使用GCC和Clang類型扣減失敗。

用於第一線GCC錯誤:

main.cpp:36:28: error: no matching function for call to ‘Proxy<Hudsucker>::call(void (Hudsucker::*)(const string&), const string&)’ 
main.cpp:36:28: note: candidate is: 
main.cpp:10:10: note: template<class A> void Proxy::call(void (T::*)(A), A) [with A = A; T = Hudsucker] 
main.cpp:10:10: note: template argument deduction/substitution failed: 
main.cpp:36:28: note: deduced conflicting types for parameter ‘A’ (‘const std::basic_string<char>&’ and ‘std::basic_string<char>’) 

尤其,該位是奇怪:no matching function for call to Proxy<Hudsucker>::call(void (Hudsucker::*)(const string&), const string&)。這正是我期望看到工作的簽名。

鏘錯誤的第一行:

main.cpp:36:7: error: no matching member function for call to 'call' 
    p.call(&Hudsucker::g, s); // <- Compile error 
    ~~^~~~ 
main.cpp:10:10: note: candidate template ignored: deduced conflicting types for parameter 'A' ('const std::basic_string<char> &' vs. 'std::basic_string<char>') 
    void call(void (T::*f)(A), A a) 

代碼:

#include <string> 
#include <iostream> 

template <typename T> class Proxy 
{ 
public: 
    Proxy(T &o): o_(o) {} 

    template <typename A> 
    void call(void (T::*f)(A), A a) 
    { 
     (o_.*f)(a); 
    } 

private: 
    T &o_; 
}; 

class Hudsucker 
{ 
public: 
    void f(std::string s) {} 
    void g(std::string const &s) {} 
}; 

int main() 
{ 
    Hudsucker h; 
    Proxy<Hudsucker> p(h); 
    std::string const s = "For kids, you know."; 
    std::string const &r = s; 

    p.call(&Hudsucker::f, s); 
    p.call(&Hudsucker::f, r); 

    p.call(&Hudsucker::g, s); // <- Compile error 
    p.call(&Hudsucker::g, r); // <- Compile error 

    return 0; 
} 

你能解釋爲什麼類型推演以這種方式失敗了呢?有沒有辦法讓這個編譯const引用?

回答

11

編譯器無法推斷出類型A,因爲它有對比的信息。從成員函數的類型來看,它將推導出Astd::string const&,而從第二個參數的類型中,它將推導出它爲std::string

改變你的函數模板爲一體,使不同類型的成員函數的參數和實際提供的參數,然後SFINAE,限制後者可轉換爲前:

template <typename A, typename B, 
    typename std::enable_if<std::is_convertible<B, A>::value>::type* = nullptr> 
void call(void (T::*f)(A), B a) 
{ 
    (o_.*f)(a); 
} 

如果您想知道爲什麼這個函數調用:

std::string const s = "For kids, you know."; 
// ... 
p.call(&Hudsucker::g, s); 

編譯器會推斷std::string,那是因爲C++ 11標準第14.8.2.1/2的:

如果P是不是引用類型

- 如果A是一個數組類型,由陣列到指針標準轉換所產生的指針類型(4.2)是替代A用於 類型扣除;否則,

- 如果A是函數類型,則由函數指針標準轉換生成的指針類型(4。3)使用 代替A進行類型扣除;否則,

- 如果A是cv限定類型,A類型的頂級cv限定符將被忽略,對於類型推導

在引用的段落,P是您A(從你的函數模板)和Astd::string const。這意味着std::string const中的const在扣除類型時會被忽略。看到這更好的,考慮這個簡單的例子:

#include <type_traits> 

template<typename T> 
void foo(T t) 
{ 
    // Does NOT fire! 
    static_assert(std::is_same<T, int>::value, "!"); 
} 

int main() 
{ 
    int const x = 42; 
    foo(x); 
} 

考慮第二個函數調用:

std::string const &r = s; 
// ... 
p.call(&Hudsucker::g, r); 

的原因是ID表達r的類型是std::string const。基準,是因爲段5/5的下降:

如果表達式最初具有類型爲「參照T」(8.3.2,8.5.3),所述類型被調整爲T之前 任何進一步分析。表達式指定由引用表示的對象或函數,並且表達式是左值或xvalue,具體取決於表達式。

現在我們回到第一個函數調用的情況。


由Mike藤在評論中指出的那樣,你可以在函數調用期間在輸入給它時第一(成員函數)的參數要完美轉發你的第二個參數

#include <utility> // For std::forward<>() 

template <typename A, typename B, 
    typename std::enable_if<std::is_convertible<B, A>::value>::type* = nullptr> 
void call(void (T::*f)(A), B&& a) 
{ 
    (o_.*f)(std::forward<B>(a)); 
} 

如果您買不起C++ 11,那麼您將不被允許爲模板參數使用默認參數。在這種情況下,你可以使用SFINAE約束返回類型:

template <typename A, typename B> 
typename enable_if<is_convertible<B, A>::value>::type 
//  ^^^^^^^^^ ^^^^^^^^^^^^^^ 
//  But how about these traits? 
    call(void (T::*f)(A), B a) 
{ 
    (o_.*f)(a); 
} 

通知書的,std::enable_ifstd::is_convertible不是C++ 03標準庫的一部分。幸運的是,升壓有它自己的enable_ifis_convertible版本,所以:

#include <boost/utility/enable_if.hpp> 
#include <boost/type_traits/is_convertible.hpp> 

template <typename T> class Proxy 
{ 
public: 
    Proxy(T &o): o_(o) {} 

    template <typename A, typename B> 
    typename boost::enable_if<boost::is_convertible<B, A>>::type 
     call(void (T::*f)(A), B a) 
    { 
     (o_.*f)(a); 
    } 

private: 
    T &o_; 
}; 

通知,即boost::enable_if接受作爲其第一個模板參數一個其限定value布爾成員,而std::enable_if接受一個布爾值。 Boost中相當於std::enable_if的是boost::enable_if_c

+0

我在這裏不是專家通過anymeans,但你不會想在這裏用完美轉發。 –

+0

@MikeVine:是的,這的確是合適的,但是在這個答案中,我想提供一個工作解決方案,只需要很少的修改,這樣OP就可以理解他原來的程序出了什麼問題(完美的轉發是一種改進,解決問題本身)。無論如何,我應該添加這個作爲一個說明,謝謝! –

+0

另外我很確定你想''type'在'* = nullptr'之前# –

4

在我看來,一個簡單的解決方案是隻排除試圖推導出兩個參數之一,第二個是更合適的人選:

template <typename A> 
void call(void (T::*f)(A), typename std::identity<A>::type a) 
{ 
    (o_.*f)(a); 
} 

如果你沒有std::identity在你的類型特點,用這一個:

template <typename T> 
struct identity { typedef T type; }; 

這也是爲什麼這個工程:編譯器不能從第二個參數推導出,因爲它只是一個模板參數,即在嵌套式取的東西。基本上,由於模板專門化,它無法將任何傳入類型與something_that_contains_A :: type進行模式匹配,因此無法從左側定義中反向設計參數。最終結果是第二個參數是「未延期的上下文」。編譯器不會嘗試從那裏推導出A.

這留下了第一個參數作爲A可以從中推導出的唯一地方。對於A只有一個扣除結果,它並不含糊,扣除成功。然後編譯器繼續將扣除結果替換爲使用A的每個地方,包括第二個參數。

+0

這實際上適用於C + + 03幾個簡單的例子。我將不得不看看是否有任何奇怪的角落案例失敗。謝謝! – detunized

+1

就像旁註一樣,沒有標準的'std :: identity'類型特徵 - 但是可以使用'std :: common_type'作爲'std :: identity'(或者像你一樣定義你自己的特徵) –

+0

現在我需要明白它爲什麼起作用了。你能解釋爲什麼這樣的竅門有幫助嗎? – detunized

1

你只需要將模板參數傳遞給模板函數時,在你的主要調用它。

int main() 
{ 
    Hudsucker h; 
    Proxy<Hudsucker> p(h); 
    std::string const s = "For kids, you know."; 
    std::string const &r = s; 

    p.call(&Hudsucker::f, s); 
    p.call(&Hudsucker::f, r); 

    //just add template argument to template function call !!! 
    p.call< const std::string & > (&Hudsucker::g, s); // <- NO Compile error !!!! 
    p.call< const std::string & > (&Hudsucker::g, r); // <- NO Compile error !!!** 

    return 0; 

}