2013-10-28 32 views
1

如何編寫一個可變參數模板,它既將const char [N] s也將std :: strings作爲參數,但根據參數類型執行不同的行爲?類型特徵來區分const char [N]和std :: string?

到目前爲止,我的可變參數模板看起來如下:

template<typename T> 
void Capitalize_And_Output(T&& str) { 
    std::transform(str.begin(), str.end(), str.begin(), ::toupper); //<- will not compile with char*s 
    std::cout << str << std::endl; 
    return; 
} 

template<typename First, typename ... Strings> 
void Capitalize_And_Output(First&& str, Strings&&... rest) { 
    std::transform(str.begin(), str.end(), str.begin(), ::toupper); //<- will not compile with char*s 
    std::cout << str << " "; 
    Capitalize_And_Output(std::forward<Strings>(rest)...); 
    return; 
} 

使用「萬能」的引用,一切都接受入功能。
但是,調用這樣的功能,將無法正常工作:

std::string hello = "hello"; 
std::string earth = "earth"; 

//fails because "planet" is a const char[N] and not a std::string 
Capitalize_And_Output(hello,"planet","earth"); //outputs: "HELLO PLANET EARTH" 

它的工作,如果我做到以下幾點:

Capitalize_And_Output(hello,std::string("planet"),"earth"); //outputs: "HELLO PLANET EARTH" 

但我不希望用戶負責制定這個轉換。我怎樣才能把這個責任傳遞給模板函數呢?

我一直在嘗試使用類型特徵做出決定,但並未成功。 我試圖用:

std::is_same<First, std::string&>::value 

,但不知道如何使分支決定。我不相信這在if語句中起作用。

也許我需要使用std ::條件? 也許我需要通過在類型爲auto的模板中創建一個局部變量來解決它& &?迄今爲止,在我嘗試的各種事情中,我還沒有取得任何成就。

+1

使用'std :: begin'和'std :: end'。實際上,字符串文字不是'char *',甚至不是'const char *'。它們是'const char [N]'。 – chris

+0

一個快速而骯髒的解決方案是將第一個參數設置爲「std :: string」而不是類型推導的參數,並依賴於現有的隱式轉換。 –

+0

@chris:但是這會給出數組的末尾(包括終止符),而不是字符串的末尾(不包括終止符)。 –

回答

1

你不需要型性狀這樣的:

char safer_toupper(unsigned char const c) 
{ 
    return static_cast<char>(std::toupper(c)); 
} 

void Capitalize_And_Output_Impl(std::string& str) 
{ 
    auto const first = str.begin(); 
    std::transform(first, str.end(), first, safer_toupper); 
    std::cout << str; 
} 

void Capitalize_And_Output_Impl(std::string const& str) 
{ 
    std::transform(str.begin(), str.end(), 
        std::ostreambuf_iterator<char>(std::cout), 
        safer_toupper); 
} 

void Capitalize_And_Output_Impl(char const* const str) 
{ 
    std::transform(str, str + std::strlen(str), 
        std::ostreambuf_iterator<char>(std::cout), 
        safer_toupper); 
} 

template<typename... Strings> 
void Capitalize_And_Output(Strings&&... rest) 
{ 
    int const unpack[]{0, (Capitalize_And_Output_Impl(rest), 
          std::cout << ' ', 0)...}; 
    static_cast<void>(unpack); 
    std::cout << std::endl; 
} 
+0

有沒有辦法通過引用來接受std :: strings,以便傳入的左值在函數調用後保持更改?我希望傳入的對象在函數結束後保持大寫(除非它們當然是字符串......) –

+0

@TrevorHickey好的,請檢查我的編輯。 – Simple

+0

謝謝!完美的作品。 static_cast是什麼(unpack);做?我評論說,並沒有看到任何地方的表現差異。噢,我認爲這是假設禁止一個零參數函數調用,但由於某種原因,它不。 –

3

我看到兩個問題與Simplesolution

(1)未編譯這個測試用例

std::string hello = "hello"; 
const std::string earth = "earth"; 
Capitalize_And_Output(hello, "planet", earth); 

因爲earthconst std::string並且沒有可以接受此呼叫的過載。 (試試吧!)

(2)未編譯可轉換爲std::string,比如類型(比const char*其他和類似的系統),

struct beautiful { 
    operator std::string() const { 
     return "beautiful"; 
    } 
}; 

Capitalize_And_Output(hello, beautiful{}, "planet", earth); 

下實現解決了這些問題:

新的解決方案:我的舊解決方案(下)工作,但它不是效率爲char*,char[N]。另外,它很複雜,並且使用一些重載解析技巧來避免含糊不清。這個更簡單,更高效。

void Capitalize_And_Output_impl(const char* str) { 
    while (char c = toupper(*str++)) 
     std::cout << c; 
} 

void Capitalize_And_Output_impl(std::string& str) { 
    std::transform(str.begin(), str.end(), str.begin(), toupper); 
    std::cout << str; 
} 

void Capitalize_And_Output_impl(const std::string& str) { 
    Capitalize_And_Output_impl(str.data()); 
} 

template<typename First> 
void Capitalize_And_Output(First&& str) { 
    Capitalize_And_Output_impl(std::forward<First>(str)); 
    std::cout << '\n'; 
} 

template<typename First, typename ... Strings> 
void Capitalize_And_Output(First&& str, Strings&&... rest) { 
    Capitalize_And_Output_impl(std::forward<First>(str)); 
    std::cout << ' '; 
    Capitalize_And_Output(std::forward<Strings>(rest)...); 
} 

因爲我不使用std::transform(除第二過載),它並不需要知道字符串的大小提前。因此,對於char*,不需要撥打std::strlen(如在其他解決方案中那樣)。

需要注意的一個小細節是,此實現僅打印單詞之間的空格。 (它不會最後一個字後,打印一張。)

老辦法

void Capitalize_And_Output_impl(std::string& str, int) { 
    std::transform(str.begin(), str.end(), str.begin(), ::toupper); 
    std::cout << str << ' '; 
} 

void Capitalize_And_Output_impl(std::string str, long) { 
    Capitalize_And_Output_impl(str, 0); 
} 

void Capitalize_And_Output() { 
    std::cout << '\n'; 
} 

template<typename First, typename ... Strings> 
void Capitalize_And_Output(First&& str, Strings&&... rest) { 
    Capitalize_And_Output_impl(std::forward<First>(str), 0); 
    Capitalize_And_Output(std::forward<Strings>(rest)...); 
} 

我想這兩個Capitalize_And_Output_impl重載值得解釋。

首先忽視第二個參數(int/long)。第一個重載可以採用在出口處大寫的非const左值(根據Trevor Hickney在Simple解決方案的評論中的要求)。

第二個過載意味着採取其他所有事情,即右值和const左值。這個想法是將參數複製到一個左值,然後傳遞給第一個重載。這個功能自然可以用這種方式實現(仍然不考慮第二個參數):

void Capitalize_And_Output_impl(const std::string& str) { 
    std::string tmp(str); 
    Capitalize_And_Output_impl(tmp); 
} 

這個工作按需要。然而,Dave Abrahams着名的article解釋說,當你通過引用const來引用參數並將其複製到你的函數中(如上所述)時,最好按值取值(因爲在某些情況下編譯器可能會避免複製) 。總之,本實施是優選的:

void Capitalize_And_Output_impl(std::string str) { 
    Capitalize_And_Output_impl(str); 
} 

不幸的是,作爲用於第一過載,則調用Capitalize_And_Output_impl上左值也可以被引導到該過載。這產生了編譯器抱怨的含糊之處。這就是爲什麼我們需要第二個參數。

第一次過載需要int,第二次需要long。因此,通過字面0,這是int,使第一次過載優於第二次,但只有當出現歧義時。在其他情況下,即當第一個參數是左值或右值時,第一個過載不能被使用,而第二個過程可以在文字0被提升爲long之後。

最後兩句話。 (1)如果你想避免在Capitalize_And_Output(我想這只是一個味道問題)的遞歸調用,那麼你可以使用與Simple的解決方案相同的詭計(通過unpack)和(2)我沒有看到需要像在Simple的解決方案中一樣傳遞包裝::toupper

0

該版本不會不必要地拷貝參數,不會引入不必要的臨時字符串,並且避免在編譯時爲長度已知的字符串調用strlen()

#include <algorithm> 
#include <cctype> 
#include <cstring> 
#include <iostream> 
#include <iterator> 
#include <string> 
#include <type_traits> 
#include <vector> 

template<typename I> void CapitalizeAndOutputImpl(I first, I last) { 
    std::string t; 
    std::transform(first, last, std::back_inserter(t), std::toupper); 
    std::cout << t << " "; 
} 

template<typename T> 
struct CapitalizeAndOutputHelper { 
    void operator()(const T& s) { 
     CapitalizeAndOutputImpl(std::begin(s), std::end(s)); 
    } 
}; 

template<typename T> 
struct CapitalizeAndOutputHelper<T*> { 
    void operator()(const T* s) { 
     CapitalizeAndOutputImpl(s, s + std::strlen(s)); 
    } 
}; 

template<typename T> void CapitalizeAndOutput(T&& s) { 
    CapitalizeAndOutputHelper<std::remove_reference<T>::type>()(s); 
    std::cout << std::endl; 
} 

template<typename First, typename... Rest> void CapitalizeAndOutput(First&& first, Rest&&... rest) { 
    CapitalizeAndOutputHelper<std::remove_reference<First>::type>()(first); 
    CapitalizeAndOutput(rest...); 
} 

int main() { 
    std::string hello{ "string hello" }; 
    const std::string world{ "const string world" }; 
    char arrHello[] = "char[] hello"; 
    const char vHelloInit[] = "char* hello"; 
    std::vector<char> vHello(std::begin(vHelloInit), std::end(vHelloInit)); 
    const char* cworld = "const char* world"; 
    CapitalizeAndOutput(hello, world, arrHello, "literal world", vHello.data(), cworld); 
} 
0

目前最簡單的是有兩個重載:

void do_stuff() {} 
template<class...Ts> 
void do_stuff(std::string s, Ts&&... ts); 

,並使用現有的身體爲第二。

我們得到完美的轉發,然後就在你的mutate和output之前,我們就複製。

如果你想讓mutate傳播出去,你可能是錯的。如果你堅持,@卡西歐的方法看起來不錯。

相關問題