2017-03-08 113 views
3

由於模板類型推導的問題,此代碼不能編譯,即使在C++ 14下也不會編譯。什麼是最不起眼的解決方法?C++中的模板函數參數14

#include <vector> 
#include <functional> 
#include <iostream> 

template <class T> 
std::vector<T> merge_sorted(
    const std::vector<T>& a, const std::vector<T>& b, 
    std::function<bool(const T, const T)> a_before_b) 
{ 
    std::vector<T> ret; 
    auto ia=a.begin(); 
    auto ib=b.begin(); 
    for (;;ia!=a.end() || ib!=b.end()) 
     ret.push_back(a_before_b(*ia,*ib) ? *(ia++) : *(ib++)); 
    return ret; 
} 

int main() 
{ 
    std::vector<double> A { 1.1, 1.3, 1.8 }; 
    std::vector<double> B { 2.1, 2.2, 2.4, 2.7 }; 
    auto f = [](const double a, const double b) -> bool { 
     return (a-(long)(a))<=(b-(long(b))); }; 
    std::vector<double> C = merge_sorted(A, B, f); 
    for (double c: C) 
     std::cout << c << std::endl; 
    // expected outout: 1.1 2.1 2.2 1.3 2.4 2.7 1.8 
} 

這裏從g++ -std=c++14 main.cpp錯誤消息:

main.cpp: In function ‘int main()’: 
main.cpp:23:49: error: no matching function for call to ‘merge_sorted(std::vector<double>&, std::vector<double>&, main()::<lambda(double, double)>&)’ 
    std::vector<double> C = merge_sorted(A, B, f); 
               ^
main.cpp:6:16: note: candidate: template<class T> std::vector<T> merge_sorted(const std::vector<T>&, const std::vector<T>&, std::function<bool(T, T)>) 
std::vector<T> merge_sorted(
       ^~~~~~~~~~~~ 
main.cpp:6:16: note: template argument deduction/substitution failed: 
main.cpp:23:49: note: ‘main()::<lambda(double, double)>’ is not derived from ‘std::function<bool(T, T)>’ 
    std::vector<double> C = merge_sorted(A, B, f); 

==

後來編輯,僅僅是爲了記錄:這裏來,編譯(感謝收到答案)代碼的版本並且正確執行(上述未經測試的代碼的若干更正):

#include <vector> 
#include <functional> 
#include <iostream> 

template <class T, class Pred> 
std::vector<T> merge_sorted(const std::vector<T>& a, const std::vector<T>& b, Pred a_before_b) 
{ 
    std::vector<T> ret; 
    auto ia=a.begin(); 
    auto ib=b.begin(); 
    for (;ia!=a.end() && ib!=b.end();) 
     ret.push_back(a_before_b(*ia,*ib) ? *(ia++) : *(ib++)); 
    for (;ia!=a.end();) 
     ret.push_back(*(ia++)); 
    for (;ib!=b.end();) 
     ret.push_back(*(ib++)); 
    return ret; 
} 

int main() 
{ 
    std::vector<double> A { 1.1, 1.3, 1.8 }; 
    std::vector<double> B { 2.1, 2.2, 2.4, 2.7 }; 
    auto f = [](const double a, const double b) -> bool { 
     return (a-(long)(a))<=(b-(long(b))); }; 
    std::vector<double> C = merge_sorted(A, B, f); 
    for (double c: C) 
     std::cout << c << std::endl; 
    // expected outout: 1.1 2.1 2.2 1.3 2.4 2.7 1.8 
} 
+4

除非你要超載merge_sorted死,我只想用一個單獨的模板參數a_before_b的類型,不需要std :: function。 –

+0

@Marc:不知道我明白 - 你能否詳細說明一下,可能是以答案的形式? –

+0

請注意,您可以在'merge_sorted'主體中重新調整'std :: merge'的位置。 –

回答

3

的這裏的問題是f不是std::function。這是一些無名的班級類型,但它不是std::function。當編譯器執行模板參數推導時,它不會進行任何轉換,它可以按照原樣來推導它們的類型。這意味着它期望看到std::function<bool(const T, const T)>它看到main()::<lambda(double, double)>,因爲這是lambda的類型,並且因爲這些類型與扣除不匹配失敗。爲了得到推論成功,你需要讓他們匹配。

在不更改函數簽名的情況下,您必須將f轉換爲std::function才能使其正常工作。這看起來就像

std::vector<double> C = merge_sorted(A, B, static_cast<std::function<bool(const double,const double)>>(f)); 

如果你不介意改變函數簽名,然後我們就可以使用

template <class T, class Func> 
std::vector<T> merge_sorted(
    const std::vector<T>& a, const std::vector<T>& b, 
    Func a_before_b) 

現在它並不重要,如果你傳遞一個std::function或λ或仿函數。

+0

很好的解釋,謝謝。我傾向於從'f'到'std :: function'。你會怎樣? std :: bind是否相關? –

+1

雖然我們在它,爲什麼不擺脫'std :: vector '並重寫它的迭代器? –

+3

@JoachimWuttke:普通模板是一種更通用的解決方案,具有更高的內聯潛力。 – Pixelchemist

3

您需要以某種方式使a_brefore_b的類型成爲非推斷的上下文。我一般來介紹一下適當命名的幫助:

template <class T> 
struct NonDeduced 
{ 
    using type = T; 
}; 

template <class T> 
std::vector<T> merge_sorted(
    const std::vector<T>& a, const std::vector<T>& b, 
    typename NonDeduced<std::function<bool(const T, const T)>>>::type a_before_b) 

當然(如@Marc Glisse在評論中指出),這是完全沒有必要強制使用的std::functiona_before_b擺在首位的類型​​。更不用提的是,它可以很容易地帶來性能損失(std::function內部使用類型擦除和動態分派)。只要按照標準庫做什麼,並通過一個模板參數類型謂詞:

template <class T, class Pred> 
std::vector<T> merge_sorted(
    const std::vector<T>& a, const std::vector<T>& b, 
    Pred a_before_b) 
-2

因爲我不能評論:一般@NathanOliver說什麼。 A lambda expression不能被「投射」到std::function,因爲它在內部是一種不同類型的結構。 當然,如果編譯器可以推斷(通過靜態分析)它必須爲lambda創建一個std::function對象,那將會很不錯。但是這似乎並不是C++ 11/C++ 14的一部分。

要解決這個問題,我覺得最簡單的一個typename添加到模板:

template <class T, typename F> 
std::vector<T> merge_sorted(
    const std::vector<T>& a, const std::vector<T>& b, 
    F& a_before_b) 

當然,你也可以使用class。見問題Use 'class' or 'typename' for template parameters?the old MSDN article here

此外,請注意你有一個錯字在13行你大概的意思是:

for (;;ia!=a.end() || ib!=b.end()) 
0
  1. 的errror來自編譯器試圖推斷T,其中它不能爲std::function參數推斷T它通過一個lambda。

  2. 該標準使用普通模板參數來表達這樣的謂詞有很好的理由。

    2.1使用模板參數的謂詞是最通用的。

    你可以通過在std::functionstd::bind,函數指針,lambda表達式,仿函數...

    2.2內聯(如果可能的話)是最有可能發生。

    有了一大堆運氣,一個編譯器很聰明,可以內聯一個lambda,儘管它是通過一個std::function傳入一個模板中的,但我不打賭。相反,如果我通過它自己的類型傳遞它,我實際上希望編譯器內聯一個lambda(如果適用)。

  3. 您的代碼還有其他一些問題。

    3.1 for (;;ia!=a.end() || ib!=b.end())這裏;設置不正確。

    3.2即使正確設置了;謂詞也是錯誤的,因爲ia!=a.end() || ib!=b.end()將保持循環運行,即使ia == a.end()ib == b.end()爲真。在循環中,如果我們已經是最後一個元素的話,那麼這兩個迭代器都被解引用來檢查引導我們進入未定義行爲狀態的謂詞。環路條件因此必須是for (;ia!=a.end() && ib!=b.end();),這使我們留下了ab中的元素。

以下是你可能會想,如果你是性能和通用性後寫:

template <class InIt, class OutIt, class Predicate> 
auto merge_sorted(InIt first1, InIt last1, InIt first2, InIt last2, 
    OutIt dest, Predicate pred) 
{ 
    // as long as we have elements left in BOTH ranges 
    for (;first1 != last1 && first2 != last2; ++dest) 
    { 
     // check predicate which range offers the lowest value 
     // and insert it 
     if (pred(*first1, *first2)) *dest = *(first1++); 
     else *dest = *(first2++); 
    } 
    // here either first1 == last1 or first2 == last2 is true 
    // thus we can savely copy the "rest" of both ranges 
    // to dest since we only have elements in one of them left anyway 
    std::copy(first1, last1, dest); 
    std::copy(first2, last2, dest); 
    return pred; 
}