2014-10-27 78 views
3

這是一個常見問題,但我找不到滿意的答案。在我的項目中,我們支持std::string,現在還必須支持寬字符串。因此,我們要搬到basic_string,但隨後,事情停止工作很好和參數需要被明確地闡明:basic_string <CharT>與CharT *

#include <string> 

template <typename CharT, typename Traits, typename Allocator> 
void 
foo(const std::basic_string<CharT, Traits, Allocator>&) 
{} 

template void foo(const std::string&); 

// template void 
// foo<char, std::char_traits<char>, std::allocator<char>>(const std::string&); 

void bar(const std::string& s) 
{} 

int main() 
{ 
    bar("abc"); 
    foo<char, std::char_traits<char>, std::allocator<char>>("def"); 
    foo("def"); 
} 

OK,它失敗了著名的理由:

clang++-mp-3.5 -Wall -std=c++11 foo.cc 
foo.cc:20:3: error: no matching function for call to 'foo' 
    foo("def"); 
    ^~~ 
foo.cc:5:1: note: candidate template ignored: could not match 
     'basic_string<type-parameter-0-0, type-parameter-0-1, type-parameter-0-2>' 
     against 'char const[4]' 
foo(const std::basic_string<CharT, Traits, Allocator>&) 
^ 

什麼我不明白它爲什麼對bar有效?爲什麼foochar(帶有顯式模板參數或帶扣除)的明確實例化不足以解決此問題?

看來,這意味着,而不是公開的API使用模板和basic_string,我們將不得不使用它作爲一個實現細節,但暴露與重載用戶爲std::stringstd::wstring等,這是一種恥辱。

謝謝!

+0

電子'foo'的xplicit版本與clang3.5一起工作,只有帶參數推演的版本(最後一行)失敗,因爲它無法推導出參數。 – Drax 2014-10-27 15:55:02

回答

5

對於bar("abc"),存在從char const[4]std::string的隱式轉換。 foobar的不同之處在於它實際上不是功能而是功能模板。爲了構建正確的功能,需要知道其模板參數。

foo第一次調用明確規定模板參數,因此它構建了一個功能,看起來像這樣:在

void foo(const std::basic_string<char, std::char_traits<char>, std::allocator<char>>&); 

的隱式轉換踢,一切都很好。

第三個電話不提供模板參數,所以編譯器必須從類型char const[4]弄清楚的CharTTraitsAllocator類型。這種類型不帶有這些信息,因此扣除失敗,重載解析無法找到正確的功能。

+0

我想我真的很沮喪,重載函數和明確實例化的函數模板行爲不一樣。謝謝! – akim 2014-10-29 08:09:37

0

最好的解決方法似乎是爲std::stringstd::wstring提供非模板重載委託給函數模板(Demo at Coliru):

template <typename CharT, typename Traits, typename Allocator> 
void foo(const std::basic_string<CharT, Traits, Allocator>&) 
{} 

inline void foo(const std::string& u) { foo<>(u); } 
inline void foo(const std::wstring& u) { foo<>(u); } 
+0

*「最好的解決方法似乎是」*爲什麼不使用'std :: wstring'文字後綴? – dyp 2014-10-27 20:19:58

+0

@dyp:當參數是一個不是文字的C風格的字符串時該怎麼辦? – 2014-10-27 20:21:17

+0

@BenVoigt啊,好點。我只考慮OP的測試用例。 – dyp 2014-10-27 20:34:51

4

這是否對你的工作:

template <typename StringT> 
void foo(const StringT& the_string) 
{ 
    typedef decltype(the_string[0]) CharT; 
    // do the work 
} 

這可以推導出StringTstd::string,std::wstring,const char[N],const wchar_t[N],std::vector<char>等。如果你想要C風格的字符串隱式轉換爲的std :: string提前,這樣你就可以使用普通的成員函數所有STL集合,添加一個轉發過載映入陣列:

template <typename CharT, size_t N> 
void foo(const CharT (&char_array_or_literal)[N]) 
{ 
    foo(std::basic_string<CharT>(char_array_or_literal)); 
} 

也許再字符指針:

template <typename CharT> 
void foo(const CharT* char_ptr) 
{ 
    foo(std::basic_string<CharT>(char_ptr)); 
} 

如果在另一方面,你需要的basic_string所有功能,那麼廣泛的模板應該用於轉發:

template <typename CharT, typename Traits, typename Allocator> 
void foo(const std::basic_string<CharT, Traits, Allocator>& the_string) 
{ 
    // the real work is done here 
} 

template <typename StringLikeT> 
void foo(const StringLikeT& the_string_like_thing) 
{ 
    typedef decltype(the_string_like_thing[0]) CharT; 
    // this turns string literals, arrays, pointers, vectors, std::array, all into basic_string 
    foo(basic_string<CharT>(&the_string_like_thing[0])); 
} 
+0

我認爲這個回答使得它最清楚發生了什麼 - 問題的代碼是模糊的東西(basic_string的細節)而不是字符串的概念。有了這個答案的解決方案,即使std :: array 和std :: vector 也可以被傳入。並且稍微偏離主題,考慮只使用UTF-8編碼的約定來堅持std :: string轉換爲需要它的API的寬字符串。 – 2014-10-28 13:48:47

+0

我很擔心使用「非受限」的參數,比如'StringT':太多其他的東西都可以進入。但是,通過對「StringT」的檢查進行一些檢查,這確實很好。謝謝! – akim 2014-10-29 08:13:29