2016-01-12 27 views
4

請考慮下面的例子(標籤調度,可變參數模板,完善的轉發,等等,都在同一個):標籤調度,可變參數模板,通用參考,並錯過常量符

#include <iostream> 
#include <utility> 
#include <string> 

struct A { }; 
struct B { }; 

void doIt(A&&, const std::string &) { 
    std::cout << "A-spec" << std::endl; 
} 

template<typename T, typename... Args> 
void doIt(T&&, Args&&...) { 
    std::cout << "template" << std::endl; 
} 

template<typename T, typename... Args> 
void fn(Args&&... args) { 
    doIt(T{}, std::forward<Args>(args)...); 
} 

int main() { 
    const std::string foo = "foo"; 
    std::string bar = "bar"; 
    fn<A>(foo); 
    fn<A>(bar); 
    fn<B>(foo); 
} 

在這種情況下,輸出是:

A-spec 
template 
template 

的原因是很明顯的,它不會打擾我。

我想要實現的是在這兩種情況下調用doIt函數的第一個實例,而不管該字符串是否具有常量說明符。
當然,一個可能的解決方案是用正確的原型定義一個新的doIt,無論如何我想知道是否還有其他解決方案。

到目前爲止,我試圖通過add_const得到它,但我確定我錯過了一些東西。
是否有任何可行的解決方案來靜默添加const說明符並使其工作?

編輯

我已經更新上面的例子中,爲了與真正的問題更加連貫。
此外,儘管有趣的答案,我忘了引用這只是一個簡化的例子,所以真正的問題不只涉及std::string。相反,對標籤A的參數(作爲示例)可能發生的參數是intconst std::string &,而對於標籤B,參數是float,類C的實例等等。
因此,那些試圖用std::string類型解決問題的答案不會解決真正的問題,我很抱歉。

+1

我認爲更改後,'doIt'的模板版本仍然應該使用完美的轉發? – Angew

+0

@skypjack是[this](http://coliru.stacked-crooked.com/a/b4beb48345f47162)你需要什麼? –

+0

@PiotrSkotnicki只有當你把所有的東西放在一個更詳細的答案中,因爲我仍然試圖找出它爲什麼起作用! :-) ......無論如何,這似乎是我在尋找的確如此。 – skypjack

回答

5

介紹兩種獨立的功能,以使它們不會彼此碰撞和編譯器不會引起任何歧義錯誤:

void doIt(A&&, const std::string &) 
{ 
    std::cout << "A-spec" << std::endl; 
} 

template <typename T, typename... Args> 
void doIt_template(T&&, Args&&...) 
{ 
    std::cout << "template" << std::endl; 
} 

優先化兩個附加重載;較好的是,它試圖調用一個目標函數的一個專門的,非模板化的版本:

template <typename T, typename... Args> 
auto fn_impl(int, Args&&... args) 
    -> decltype(doIt(T{}, std::forward<Args>(args)...), void()) 
{ 
    doIt(T{}, std::forward<Args>(args)...); 
} 

template <typename T, typename... Args> 
void fn_impl(char, Args&&... args) 
{ 
    doIt_template(T{}, std::forward<Args>(args)...); 
} 

介紹一個單一的,一般調度:

template <typename T, typename... Args> 
void fn(Args&&... args) 
{ 
    fn_impl<T>(0, std::forward<Args>(args)...); 
} 

DEMO

+0

我是錯的還是'decltype'在解析過程中被用來強制'SFINAE',這樣'fn_impl'的第一個實現會給那些沒有專門化的標記提供一個錯誤? – skypjack

+0

@skypjack是的,這是一個表達式SFINAE,它首先被強制檢查,因爲它需要'int'作爲它的第一個參數,它在'fn_impl (0,...''' –

+0

明白了,謝謝,那就是我一直在尋找的東西,恭喜,真的是一個很好的答案。 – skypjack

4

重載轉發引用是一個痛苦,因爲它們非常非常貪婪。

一種辦法是禁用模板版本,如果Args...是單一參數轉換爲std::string

template<typename... Args> 
struct convertible_to_single_string { 
    static std::true_type help (std::string); 
    static std::false_type help (...); 
    using type = decltype(help(std::declval<Args>()...)); 
    static constexpr auto value = type::value; 
}; 

template<typename... Args> 
std::enable_if_t<!convertible_to_single_string<Args...>::value, void> 
doIt(int, Args&&...) { 
    std::cout << "template" << std::endl; 
} 

這也叫串版本const char*或任何與用戶定義的轉換到std::string。如果你不想這樣做,還有其他的可能性,比如從類型中刪除所有引用和cv限定符,並檢查它是否爲std::is_same<std::string, T>

Live Demo

+0

謝謝,不幸的是,真正的問題比'std :: string'類型的簡單參數更復雜。請查看更新後的問題。 – skypjack

1
template<class String, 
    std::enable_if_t<std::is_same<std::decay_t<String>,std::string>{},int> =0 
> 
void doIt(int, String&&) { 
    std::cout << "std::string" << std::endl; 
} 

這將使用轉發引用,然後SFINAE得到任何string類型只在裏面。

如果你願意將你的參數綁定到一個元組,你也可以這樣做usimg標籤分派。 is_same可以替換爲is_convertible,然後可以使用具有不同名稱的實際轉換爲std::string的幫助函數。

另一種方法是在第二層爲標記保留第一個參數,然後手動計算出您想要的重載並生成該標記類型。

+0

關於最後的解決方案,是不是含糊不清?常規資格與用戶定義的轉換? –

+0

謝謝,不幸的是,真正的問題比'std :: string'類型的簡單參數更復雜。請查看更新後的問題。 – skypjack

+0

@PiotrSkotnicki Hurm,你是對的:出於某種奇怪的原因,我認爲constness轉換與用戶定義的轉換不同。我錯了。 – Yakk