2014-01-27 148 views
2

這是一個帶有可變參數構造函數的類,它是專門用於從臨時文件複製和移動的類。類模板的可變參數構造函數模板的專門化

template<class Obj> 
    class wrapper { 
    protected: 
     Obj _Data; 
    public: 

     wrapper(const wrapper<Obj>& w): _Data(w._Data) {} 

     wrapper(wrapper<Obj>&& w): 
      _Data(std::forward<Obj>(w._Data)) {} 

     template<class ...Args> 
     wrapper(Args&&... args): 
      _Data(std::forward<Args>(args)...) {} 

     inline Obj& operator()() { return _Data; } 

     virtual ~wrapper() {} 
    }; 

當我使用專業化的一個這樣的

wrapper<int> w1(9); 
wrapper<int> w2(w1); 

,我發現了錯誤:W1的類型推斷爲int。從VS2012

輸出:

error C2440: 'initializing' : cannot convert from 'win::util::wrapper<int>' to 'int' 

如何解決這個問題呢?

回答

6

你被貪婪的完美轉發構造函數咬住了。

wrapper<int> w2(w1); 

在上面的行中,完善轉發構造是更好的匹配相比拷貝構造爲,因爲Args推導爲wrapper<int>&

速戰速決的解決辦法是改線以上

wrapper<int> w2(static_cast<wrapper<int> const&>(w1)); 

這個正確調用拷貝構造函數,但是,除了是不必要的冗長,沒有解決根本問題。

要解決原始問題,當Argswrapper<Obj>相同時,您需要有條件地禁用完美轉發構造函數。

Here's一篇優秀的博客文章,描述問題以及如何解決問題。總之,你需要完美的轉發構造函數定義修改爲

template <typename... Args, 
      DisableIf<is_related<wrapper<Obj>, Args...>::value>...> 
wrapper(Args&&... args): 
    _Data(std::forward<Args>(args)...) {} 

其中is_related被定義爲

template <typename T, typename... U> 
struct is_related : std::false_type {}; 

template <typename T, typename U> 
struct is_related<T, U> : std::is_same<Bare<T>, Bare<U>> {}; 

Bare

template <typename T> 
using Bare = RemoveCv<RemoveReference<T>>; 

RemoveCvRemoveReferencestd::remove_cv別名模板和std::remove_reference

Live demo

+0

不應該是'is_related ,Args >> ...'?另外,爲什麼它應該禁用,如果類型是相同的? – 0x499602D2

+0

@ 0x499602D2不,'args'需要在'is_related'內擴展,因爲您希望在sizeof ...(Args)!= 1'時選擇主'is_related'模板。當類型相同時,您想禁用它,因爲在這種情況下,您希望選擇複製/移動構造函數而不是完美的轉發構造函數。 – Praetorian

+0

哦,我沒有注意到主模板有模板包。無論如何,你是說如果一個'包裝器'包裝 w(x,y,z)'其中'{x,y,z}'都是類型'Obj'(意思是'int'),那麼構造器模板* shouldn不會被選中?我認爲你認爲他們不應該因爲你使用'DisableIf'。但我認爲它應該是'EnableIf'。 – 0x499602D2

3

編譯器實例化在這條線的構造模板:

wrapper<int> w2(w1); 

由於w1類型是wrapper<int>&和重載決策規則dicate該精確匹配是優選的轉化率。採用const wrapper<Obj>&的構造函數需要const資格,而wrapper<Obj>&&是一個右值引用,它不能綁定到左值。

通常情況下,非模板重載比模板的人(因此在正常情況下的拷貝構造將被選擇)一個優選的目標,但由於構造模板需要universal reference,它可以推斷出類型作爲int製造一個完美的匹配,因此被選中,因此當參數被轉發時出現錯誤。

作爲修復,您可以在某些上下文中通過SFINAE禁用完美轉發構造函數,如this article@Praetorian's answer中所述。

+0

這有點急劇溶液,是嗎?創建諸如這些的包裝時,完美的轉發構造函數非常方便。您可以使用SFINAE來禁用它們以防止此問題。 R.MartinhoFernandes描述瞭如何在[this](http://flamingdangerzone.com/cxx11/2012/06/05/is_related.html)文章中做到這一點。 – Praetorian

+0

@Praetorian有趣!好吧,我會改變它。 :) – 0x499602D2

+0

Nit-pick:直接將表達式綁定到'const wrapper &&(即不創建臨時)仍然排名爲完全匹配。 [over.ics.rank]/3中有一個特殊規則可以消除這種情況。類似地,如果所有參數轉換具有相同的「等級」(非等級更差,等級是其中的一部分),則非模板函數僅優於函數模板[over.match.best]/1。 – dyp

1

對我來說,使用的Praetorian的例子更細化的版本爲我工作。 我定義了類似is_compat<T, Arg>的東西,然後將其輸入std::enable_if<>表達式(利用std::decay<>來簡化匹配)。

編輯:發現std::is_convertible,簡單得多。

自包含示例:

內嵌例子:

#include <type_traits> 
// Syntactic sugar 
using std::enable_if; 
using std::is_convertible; 
template<bool Expr, typename Result = void> 
using enable_if_t = typename enable_if<Expr, Result>::type; 
template<typename From, typename To> 
using enable_if_convertible_t = enable_if_t<is_convertible<From, To>::value>; 

然後,你可以做的過載,如:

template<typename ... Args> 
void my_func(Args&& ... args) { 
    cout << "1. my_func<Args...>(" << name_trait_list<Args&&...>::join() << ")" << endl; 
} 

// Use template with enable_if to catch as many types as possible 
template<typename T1, 
    typename = enable_if_convertible_t<T1, string>> 
void my_func(int y, T1&& z) { 
    cout 
     << "2. my_func<T1:string>(int, " << name_trait<decltype(z)>::name() 
     << ")" << endl; 
} 

// Example using multiple types (let compiler handle the combinatorics) 
template<typename T1, typename T2, 
    typename = enable_if_t<is_convertible<T1, string>::value && 
          is_convertible<T2, double>::value>> 
void my_func(int y, T1&& z, T2&& zz) { 
    cout 
     << "3. my_func<T1:string, T2:double>(int, " 
     << name_trait<decltype(z)>::name() << ", " 
     << name_trait<decltype(zz)>::name() << ")" << endl; 
} 

(注:name_trait*是一個家庭烘焙類)

輸出示例:

>>> (my_func(1, 2, 5, string("!!!"))); 
1. my_func<Args...>(int&&, int&&, int&&, std::string&&) 

>>> (my_func(3, string("Hello"))); 
2. my_func<T1:string>(int, std::string&&) 

>>> (my_func(4, (const string&)string("kinda"))); 
2. my_func<T1:string>(int, const std::string&) 

>>> (my_func(5, "World")); 
2. my_func<T1:string>(int, const char[6]&) 

>>> (my_func(6, var)); 
2. my_func<T1:string>(int, char[6]&) 

>>> (my_func(7, var, 12)); 
3. my_func<T1:string, T2:double>(int, char[6]&, int&&) 

>>> (my_func(9, var, 12.0)); 
3. my_func<T1:string, T2:double>(int, char[6]&, double&&) 
+1

相關的單個參數時可能是未定義的。作爲替代方案,您可以嘗試使用'&& ',類似'enable_if_t && is_compat_v '。 – eacousineau

+0

不,不能,至少C++ 1y。我的嘗試:[diff for tpl_spec_greedy.cc](https://github.com/EricCousineau-TRI/repro/commit/b425bac)。 – eacousineau

+0

第二次更新:是的,你可以;我只是在模板化的'using'語句中做了一些錯誤。 – eacousineau