2014-10-10 68 views
3

下面的函數重載是不明確的。我發現std::function可以constructed from most callable types,即使他們的簽名不匹配。所以編譯器無法知道要使用哪個函數。如何使這些std :: function參數明確?傳遞的λ時

template <typename T> void each(std::function<void(T)> iterator); 
template <typename T> void each(std::function<void(T, id)> iterator); 
template <typename T> void each(std::function<void(T&)> iterator); 
template <typename T> void each(std::function<void(T&, id)> iterator); 

這裏有一些類似的問題,但沒有一個能解決我的問題。如何在不改變用途的情況下解決歧義問題?此外,當時我不得不明確提到模板類型。有沒有解決的辦法?

+3

它不會匹配lambda表達式,編譯器無法推導出'T'。 – Jamboree 2014-10-10 12:17:24

+0

@Jamboree當我刪除前三個重載,它編譯罰款'manager.entity.each ([=](type :: window&window,id entity){/ * ... * /} );' – danijar 2014-10-10 12:20:22

+0

哦,好的,所以你明確指定'T'。在C++ 14中,'std :: function'是sfinae友好的,所以它不會被那些不可調用的構造。 – Jamboree 2014-10-10 12:25:00

回答

6

這其中的一半是LWG issue 2132,從重載解析中移除std::function的構造函數,除非參數實際上可以爲指定的參數類型調用。這需要表達SFINAE支持才能實現,哪些VC++沒有。

問題的另一半是重載解析:

#include<functional> 
#include<iostream> 
struct id {}; 
template <typename T> void each(std::function<void(T)>){ std::cout << __PRETTY_FUNCTION__ << std::endl; } 
template <typename T> void each(std::function<void(T, id)>){ std::cout << __PRETTY_FUNCTION__ << std::endl; } 
template <typename T> void each(std::function<void(T&)>){ std::cout << __PRETTY_FUNCTION__ << std::endl; } 
template <typename T> void each(std::function<void(T&, id)>){ std::cout << __PRETTY_FUNCTION__ << std::endl; } 
int main() { 
    each<int>([](int, id){}); 
} 

用實現LWG2132,this code prints,也許令人驚訝的庫:

void each(std::function<void(T&, id)>) [with T = int] 

爲什麼?首先,可以從[](int, id){}構建std::function<void(T&, id)>。畢竟,後者可以被稱爲與int類型的左值就好了。

其次,在

template <typename T> void each(std::function<void(T, id)>); 
template <typename T> void each(std::function<void(T&, id)>); 

第二個比第一個由函數模板部分排序規則更加專業化的,所以它總是通過重載決議選擇。


一個可能的解決方案是通過操縱類型提取簽名拉姆達的operator()

template<class T> 
struct mem_fn_type; 
template<class R, class C, class... T> 
struct mem_fn_type<R(C::*)(T...)> { 
    using type = std::function<R(T...)>; 
}; 
template<class R, class C, class... T> 
struct mem_fn_type<R(C::*)(T...) const> { 
    using type = std::function<R(T...)>; 
}; 

// optional extra cv-qualifier and ref-qualifier combos omitted 
// since they will never be used with lambdas  

// Detects if a class is a specialization of std::function 
template<class T> 
struct is_std_function_specialization : std::false_type {}; 

template<class T> 
struct is_std_function_specialization<std::function<T>> : std::true_type{}; 

// Constrained to not accept cases where T is a specialization of std::function, 
// to prevent infinite recursion when a lambda with the wrong signature is passed 
template<class T> 
typename std::enable_if<!is_std_function_specialization<T>::value>::type each(T func) { 
    typename mem_fn_type<decltype(&T::operator())>::type f = func; 
    each(f); 
} 

這不會對通用Lambda表達式工作(其operator()是一個模板)或任意函數對象(可能會有任意多個過載)。

+0

是不是有一種方法可以支持前半部分的問題,通過使用像'template void each(T iterator);'並且在內部轉換爲'std :: function'的簽名? – danijar 2014-10-10 13:37:25

+0

@danijar請參閱編輯。 – 2014-10-10 14:34:57

+0

謝謝。您能否添加一個使用示例請問我在問題中提供的簽名?目前,我的理解不足以應用您的解決方案。 – danijar 2014-10-10 21:33:09

1

Morever,當時我已經明確提到的模板類型。有沒有解決的辦法?

規範的解決方案是實現一個通用的依賴注入點(即單重載),並允許客戶端代碼決定它放在那裏。我不確定如何給出一個對你提供的代碼有意義的例子,因爲我無法想象一個稱爲each的函數會使用一個名爲iterator的參數(當該參數是一個返回void的函子時)。

與你相似的函數將被應用於訪問者模式,所以我會使用,給你一個例子:

class collection { 
    std::vector<int> data; // to be visited 
public: 
    void visit(std::function<void(int)> visitor) // single overload 
    { 
     std::for_each(std::begin(data), std::end(data), visitor); 
    } 
}; 

void complex_visitor(int element, double EXTRA, char* PARAMETERS, bool HERE); 

客戶端代碼:

collection c; 
char* data = get_some_data(); 
bool a = false; 
c.visit([&](int x) { complex_visitor(x, .81, data, a); }); 

在這個例子中,它是在最後一行(即在客戶端代碼中),你決定如何插入一個不匹配的訪問者,而不是在collection類的接口。

+0

是的,我使用訪客模式。也許我不應該從這個例子中刪除太多。但是,我有四個(沒有區別價值和參考兩個)模板化的重載。那就是問題所在。 – danijar 2014-10-10 13:34:44

+0

如果四個重載實現相同的算法,我認爲你應該有一個單一的實現;如果他們實現不同的算法,他們會做不同的事情,並且可能應該以不同的方式命名(從而完全避免衝突)。我無法想象這樣一種情況,即功能應該被稱爲相同但完全不同的事情(並且在那種情況下,我沒有解決方案 - 至少在六個月內沒有變成一個難以理解的維護噩夢: () – utnapistim 2014-10-10 14:44:47

+0

正如一些人所指出的那樣,我不能僅僅通過引用而不是一個值來重載,所以我留下了兩個重載,爲訪問者提供了額外的'id'參數,我可以有一個單一的實現同時支持,一個參數爲'T'的lambda表達式和兩個參數'T,id'的lambdas? – danijar 2014-10-10 15:26:05

相關問題