2013-10-03 56 views
3

雖然有很多東西在那裏浮出來關於獲取任何模板化回調函數/方法(當然包括lambda當然)的返回類型,但我很難找到信息關於解析lambda函數的完整調用簽名。至少在gcc 4.7中,這似乎是一個正常竅門(見下文)不起作用的邊緣情況。這裏就是我想要做的,到目前爲止(當然是一個精簡版)...使用可變參數模板參數來解析lambda簽名

template<typename Sig> 
struct invokable_type { }; 

template<typename R, typename...As> 
struct invokable_type<R(As...)> { 
    static constexpr size_t n = sizeof...(As); 
    typedef R(callable_type)(As...); 
    template<size_t i> 
    struct arg { 
     typedef typename peel_type<i, As...> type; 
    }; 
}; 

peel_type<size_t, typename...>是不是在這裏爲簡便起見包括在內,但它是一個簡單的參數類型削皮器(我覺得有一個建在C + + 11,但我從來沒有打擾看)。這個問題並不重要。

然後,當然,特(和進一步的性質/類型定義),用於可調用類型如R(*)(As...)R(&)(As...)(R(T::*)(As...)std::function<R(As...)>,方法CV限定符,方法左值/右值限定符,等,等,等無數存在

然後,某處在路上,我們有一個可愛的函數或方法(這裏的功能,無所謂),看起來像......

template<typename C, typename...As> 
static void do_something(C&& callback, As&&...as) { 
    do_something_handler<invokable_type<C>::n, As...>::something(std::forward<C>(callback), std::forward<As>(as)...); 
} 

別說什麼do_something_handler呢?這完全無關緊要。問題在於lambda函數。

對於我專門爲其設計的所有可能的通用可調用簽名(它看起來都是非STL仿函數),當do_something()作爲第一個參數(模板演繹完全有效)被調用時,它會很好地工作。但是,lambda函數會導致未捕獲的類型簽名,導致使用invokable_type<Sig>,這意味着像::n::args<0>::type這樣的東西根本不存在。

不-A-問題例如...

void something(int x, int y) { 
    return x * y; 
} 

......後來......

do_something(something, 7, 23); 

問題例如...

do_something([](int x, int y) { 
     return x * y; 
    }, 7, 23); 

如果我沒有理解lambda函數正確,編譯器可能將此lambda編譯爲靜態函數定義範圍的「命名空間」(gcc當然似乎)。對於我的生活,我無法弄清楚簽名實際上是什麼。它看起來確實有一個應該通過模板專業化(基於錯誤報告)來推理。

另一個問題是即使有我可以使用的簽名,交叉編譯器如何危險? lambda編譯簽名是標準化還是全面?

+0

lambda表達式的類型是帶有函數調用操作符的類類型(閉包類型)。不過,我不確定'typedef R(invokable_type)(As ...)'是什麼。 – dyp

+0

@DyP:lambda表達式*不是*明確的類類型。在C++ 11規範中沒有這樣的要求,如果沒有捕獲完成,肯定不需要。請指出規範,如果你知道這是事實,我立場糾正(並有一些工作,這是很好:)。至於typedef,它是該函數簽名的裸typedef。它不是一個指針或ref函數,或者它們分別是'typedef R(* invokable_type)(As ...)'和'typedef R(&invokable_type)(As ...)'。就像我說的那樣,我專門爲這一切做了準備。 –

+1

[expr.prim.lambda]/3「* lambda表達式*(也是閉包對象的類型)的類型是唯一的,未命名的非聯合類類型 - 稱爲*閉包類型*」 – dyp

回答

5

總結並從評論延伸:

每[expr.prim。λ/ 3,λ-表達的類型是一個類類型,就像 「普通,命名功能對象類型」:

The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type — called the closure type [...]

再往下,/ 5指定:

The closure type for a lambda-expression has a public inline function call operator (13.5.4) whose parameters and return type are described by the lambda-expression’s parameter-declaration-clause and trailing-return-type respectively. This function call operator is declared const (9.3.1) if and only if the lambda-expression’s parameter-declaration-clause is not followed by mutable. It is neither virtual nor declared volatile . [...]

(它然後通過指定屬性和異常規範繼續)

這意味着拉姆達[](int p){ return p/2.0; }的行爲在這方面酷似

struct named_function_object 
{ 
    double operator() (int p) const { return p/2.0; } 
}; 

因此,你的第一個特

template<typename R, typename...As> 
struct invokable_type<R(As...)>; 

應該已經能夠對付lambda表達式。 SSCCE

#include <utility> 

template<class T> 
struct decompose; 

template<class Ret, class T, class... Args> 
struct decompose<Ret(T::*)(Args...) const> 
{ 
    constexpr static int n = sizeof...(Args); 
}; 

template<class T> 
int deduce(T t) 
{ 
    return decompose<decltype(&T::operator())>::n; 
} 

struct test 
{ 
    void operator() (int) const {} 
}; 

#include <iostream> 
int main() 
{ 
    std::cout << deduce(test{}) << std::endl; 
    std::cout << deduce([](int){}) << std::endl; 
} 

很好地編譯了最新版本的clang ++和g ++。看來這個問題與g ++有關。4.7進一步的研究表明g ++ - 4.7.3編譯上面的例子。

該問題可能與誤認爲lambda表達式會產生函數類型有關。如果我們定義do_something作爲

template<class C> 
void do_something(C&&) 
{ 
    std::cout << invokable_type<C>::n << std::endl; 
} 

然後對於像do_something([](int){})的呼叫時,模板參數C將被推導爲閉合型(無附圖),即一類的類型。上面定義的struct test的類似情況將是do_something(test{}),在這種情況下,C將被推斷爲test。因此

被實例化的invokable_type的專業化是一般情況下

template<class T> 
struct invokable_type; 
在這兩種情況下作爲

T不是「混合型」等的指針或功能類型。這是一般的情況下,可以通過假設只需要純類類型,然後使用構件類類型的T::operator()使用:

template<class T> 
struct invokable_type 
{ 
    constexpr static int n = invokable_type<&T::operator()>::n; 
}; 

,或者如Potatoswatter所說的那樣,通過繼承

template<class T> 
struct invokable_type 
    : invokable_type<&T::operator()> 
{}; 

Potatoswatter's version然而,更爲一般而且可能更好,依靠SFINAE檢查存在T::operator(),如果找不到操作員,它可以提供更好的診斷信息。

N.B.如果你在一個沒有捕獲任何東西的lambda表達式前面加上一個單引號+,它將被轉換爲一個指向函數的指針。 do_something(+[](int){})將與專業invokable_type<Return(*)(Args...)>一起工作。

+0

樂嘆...到C++ 14結束時,C++ 11仍然不會在主要編譯器/發行版中完成。我並不抱怨未來:) –

+0

他的代碼如何處理lambdas ...我沒有看到任何提及'T :: operator()'的東西。你需要的是,給定一個類型,如果有的話,得到它的成員operator()的簽名。我對Q中所有過度的樣板感到困惑。至於這個答案,你已經在'T()'中計算了參數列表的大小,並且沒有碰到lambda。 – Potatoswatter

+0

@Potatoswatter:它不在這個簡單的例子中(正如問題中所述),但問題與你不能做'auto fn = std :: function([](){})'在海灣合作委員會4.7和正確解決,沒有指定模板參數...這是根本問題。 –

0

由於DyP提到,lambda仿函數保證有一個公開的operator()。請注意,operator()不能是static或非成員。

(A型,也可以調用由於轉換運營商的存在爲一個函數指針,和無國籍的lambda確實有這樣的轉換運營商,但他們仍然必須提供operator()

你可以得到的簽名operator()使用decltype(& T::operator()),前提是隻有一個過載,這是保證lambda表達式的結果,指向成員函數類型,可以使用元函數去掉T::部分,或者直接寫入PTMF的元函數查詢

#include <iostream> 
#include <typeinfo> 
#include <type_traits> 
#include <tuple> 

template< typename t, std::size_t n, typename = void > 
struct function_argument_type; 

template< typename r, typename ... a, std::size_t n > 
struct function_argument_type< r (*)(a ...), n > 
    { typedef typename std::tuple_element< n, std::tuple< a ... > >::type type; }; 

template< typename r, typename c, typename ... a, std::size_t n > 
struct function_argument_type< r (c::*)(a ...), n > 
    : function_argument_type< r (*)(a ...), n > {}; 

template< typename r, typename c, typename ... a, std::size_t n > 
struct function_argument_type< r (c::*)(a ...) const, n > 
    : function_argument_type< r (c::*)(a ...), n > {}; 

template< typename ftor, std::size_t n > 
struct function_argument_type< ftor, n, 
    typename std::conditional< false, decltype(& ftor::operator()), void >::type > 
    : function_argument_type< decltype(& ftor::operator()), n > {}; 


int main() { 
    auto x = [](int, long, bool){}; 
    std::cout << typeid(function_argument_type< decltype(x), 0 >::type).name() << '\n'; 
    std::cout << typeid(function_argument_type< decltype(x), 1 >::type).name() << '\n'; 
    std::cout << typeid(function_argument_type< decltype(x), 2 >::type).name() << '\n'; 
} 

http://coliru.stacked-crooked.com/a/57cd7bb76267ffda

+0

你可以重做這個調用,就像constexpr arg count n被傳遞給另一個處理程序的例子一樣,除了C之外的任何回調函數都是已知的(Callable type,C &&在這個例子中)? –

+0

@ mr.stobbe「你可以重做這個」不是一個有禮貌的介紹。我從你的問題中提供了'invokable_type :: arg'的功能。如果你想將它封裝在其他東西中,這取決於你。 – Potatoswatter

+0

嗯,我不認爲這特別不禮貌,但如果我冒犯了你,我很抱歉。它有效,但是這是'auto x =λ',這不是問題的關鍵。如果你用'decltype([](int,long,bool){})'代替'decltype(x)',它不起作用。 –