2016-09-19 44 views
11

假設我們有以下的情況:爲什麼在std :: copy_if簽名不限制謂詞類型

struct A 
{ 
    int i; 
}; 

struct B 
{ 
    A a; 
    int other_things; 
}; 

bool predicate(const A& a) 
{ 
    return a.i > 123; 
} 

bool predicate(const B& b) 
{ 
    return predicate(b.a); 
} 

int main() 
{ 
    std::vector<A> a_source; 
    std::vector<B> b_source; 

    std::vector<A> a_target; 
    std::vector<B> b_target; 

    std::copy_if(a_source.begin(), a_source.end(), std::back_inserter(a_target), predicate); 
    std::copy_if(b_source.begin(), b_source.end(), std::back_inserter(b_target), predicate); 

    return 0; 
} 

兩個調用std::copy_if產生編譯錯誤,因爲predicate()功能的正確超載不能infered通過自std::copy_if模板簽名,編譯器接受任何類型的謂詞:

template<typename _IIter, 
     typename _OIter, 
     typename _Predicate> 
_OIter copy_if(// etc... 

我發現,如果我換行std::copy_if呼叫到一個更constra重載解析工作模板功能:

template<typename _IIter, 
     typename _OIter, 
     typename _Predicate = bool(const typename std::iterator_traits<_IIter>::value_type&) > 
void copy_if(_IIter source_begin, 
       _IIter source_end, 
       _OIter target, 
       _Predicate pred) 
{ 
    std::copy_if(source_begin, source_end, target, pred); 
} 

我的問題是:爲什麼在STL中它是不是已經被這樣約束了?從我所看到的情況來看,如果_Predicate類型不是返回bool並且接受迭代的輸入類型的函數,它仍然會產生編譯器錯誤。那麼爲什麼不把這個限制放在簽名中,以便重載解析可以工作?

+1

您的約束太強(不需要'const',允許一些轉換('int'到'bool'))。 'decltype'將允許正確的需求(或概念),但該方法在C++ 11之前完成。 – Jarod42

回答

12

因爲謂詞不一定是一個函數,但它也可以是函子。限制函子類型幾乎是不可能的,因爲只要定義了operator()它就可以是任何東西。

其實我建議你轉換的重載函數多態仿函數在這裏:

struct predicate { 
    bool operator()(const A& a) const 
    { 
     return a.i > 123; 
    } 

    bool operator()(const B& b) const 
    { 
     return operator()(b.a); 
    } 
} 

,並調用函子與一個實例,即

std::copy_if(a_source.begin(), a_source.end(), std::back_inserter(a_target), predicate()); 
std::copy_if(b_source.begin(), b_source.end(), std::back_inserter(b_target), predicate()); 
//                      ^^ here, see the() 

那麼正確的過載將裏面的選擇算法。

+0

這非常有趣。兩個問題:(1)爲什麼在這種情況下選擇了正確的超載? (2)是否可以使'operator()'靜態? – nyarlathotep108

+2

@ nyarlathotep108,ad(1),模板現在在一個包中同時獲得兩個重載,並且選擇發生在模板內部深處,實際上有足夠的信息來完成它。廣告(2),是的,它應該是,但它不會有太大的區別,因爲無論如何您都需要創建虛擬實例。 –

+0

實際上,lambda表達式是根據函數對象定義的,並且只有非捕獲的lambdas已經轉換爲定義的函數指針(您必須將捕獲的值存儲在某個地方!) – JohannesD

4

此問題不僅影響算法的謂詞。它發生在模板類型演繹推演出重載函數的任何地方。模板類型推理髮生在重載解析之前,因此編譯器缺乏解決歧義的上下文信息。

正確書寫的約束將會非常複雜,因爲它需要考慮參數和返回類型轉換,綁定,lambda表達式,函子,mem_fn s等等。

解決歧義(IMHO)的簡單方法是通過lambda調用謂詞。

std::copy_if(a_source.begin(), a_source.end(), 
     std::back_inserter(a_target), 
     [](auto&& x){ return predicate(std::forward<decltype(x)>(x)); }); 

這會延遲重載分辨率,直到模板類型扣除後。

如果我拒絕(或我的老闆拒絕)升級到C++ 14

然後用手卷相同的λ:

struct predicate_caller 
{ 
    template<class T> 
    decltype(auto) operator()(T&& t) const 
    { 
    return predicate(std::forward<T>(t)); 
    } 
}; 

,並呼籲像這樣:

std::copy_if(b_source.begin(), b_source.end(), 
      std::back_inserter(b_target), 
      predicate_caller()); 
+0

然而,這隻適用於C++ 14通用lambda表達式。這是足夠新的,它可能無法在您的本地編譯器中使用。 –

+0

@JanHudec我無法想象一個C++ 11編譯器,它不會輕鬆升級到C++ 14。我的觀點是,保持與C + + 11本身就是一種反模式。但是,具有模板化調用操作符的函數對象就足夠了。 –

+0

@JanHudec爲所有頑固派提供了手卷模板函子。 –

相關問題