2017-04-30 63 views
3

我目前有一個系統「連接」signal s功能。這個signal是一個可變參數模板,它有connect到的函數參數作爲模板參數。有沒有辦法部分匹配variadic模板參數包?

在當前的實現中,我顯然無法連接到參數不完全相同(或可以轉換爲那些參數)作爲signal的參數的函數。現在,當我試圖模仿Qt的signal/slot/connect,我也喜歡Nsignal一個參數連接到M<Nslot一個參數,這是完全明確的(即忽略的信號>M參數並將第一個M傳遞給連接的函數)。有關我最簡單的代碼示例,請參閱Coliru

所以現在的問題是雙重的:

  1. 如何讓connect呼叫工作功能void g(int);
  2. 如何使emit呼叫適用於功能void g(int);

我猜我不得不做出對雙方slot和通話功能的一些「神奇」參數包減速機,但我不知道它是如何應該適合在一起,這是相當難以實際開始嘗試編碼解決方案。如果至少Clang/GCC和Visual Studio 2015可以編譯它,我可以使用C++ 17-only解決方案。

上面鏈接的完整性的代碼:

#include <memory> 
#include <vector> 

template<typename... ArgTypes> 
struct slot 
{ 
    virtual ~slot() = default; 

    virtual void call(ArgTypes...) const = 0; 
}; 

template<typename Callable, typename... ArgTypes> 
struct callable_slot : slot<ArgTypes...> 
{ 
    callable_slot(Callable callable) : callable(callable) {} 

    void call(ArgTypes... args) const override { callable(args...); } 

    Callable callable; 
}; 

template<typename... ArgTypes> 
struct signal 
{ 
    template<typename Callable> 
    void connect(Callable callable) 
    { 
     slots.emplace_back(std::make_unique<callable_slot<Callable, ArgTypes...>>(callable)); 
    } 

    void emit(ArgTypes... args) 
    { 
     for(const auto& slot : slots) 
     { 
      slot->call(args...); 
     } 
    } 

    std::vector<std::unique_ptr<slot<ArgTypes...>>> slots; 
}; 

void f(int, char) {} 

int main() 
{ 
    signal<int, char> s; 
    s.connect(&f); 

    s.emit(42, 'c'); 
} 
+0

請注意,我想保留第一個'M'參數在任何情況下的嚴格類型檢查,在取消類型安全性中沒有用處。另一方面,如果我被迫解決了將函數調用轉發給實際可調用對象並實現一個「不可能的快速委託」的'虛擬'簡單性,我會考慮它,因爲看起來這種類型的東西關係我很難。 – rubenvb

回答

3
template<class...> struct voider { using type = void; }; 
template<class... Ts> using voidify = typename voider<Ts...>::type; 

template<class C, class...Args> 
using const_lvalue_call_t = decltype(std::declval<const C&>()(std::declval<Args>()...)); 

template<class T, std::size_t...Is> 
auto pick_from_tuple_impl(T &&, std::index_sequence<Is...>) 
    -> std::tuple<std::tuple_element_t<Is, T>...>; 

template<class Tuple, class = std::enable_if_t<(std::tuple_size<Tuple>::value > 0)>> 
using drop_last = decltype(pick_from_tuple_impl(std::declval<Tuple>(), 
         std::make_index_sequence<std::tuple_size<Tuple>::value - 1>())); 

template<class C, class ArgsTuple, class = void> 
struct try_call 
    : try_call<C, drop_last<ArgsTuple>> {}; 

template<class C, class...Args> 
struct try_call<C, std::tuple<Args...>, voidify<const_lvalue_call_t<C, Args...>>> { 
    template<class... Ts> 
    static void call(const C& c, Args&&... args, Ts&&... /* ignored */) { 
     c(std::forward<Args>(args)...); 
    } 
}; 

然後在callable_slot

void call(ArgTypes... args) const override { 
    using caller = try_call<Callable, std::tuple<ArgTypes...>>; 
    caller::call(callable, std::forward<ArgTypes>(args)...); 
} 

對於構件指針支持(這需要SFINAE友好std::result_of),更改const_lvalue_call_t

template<class C, class...Args> 
using const_lvalue_call_t = std::result_of_t<const C&(Args&&...)>; 

然後在try_call::call實際調用更改爲

std::ref(c)(std::forward<Args>(args)...); 

這是窮人的std::invoke爲左值可調用。如果你有C++ 17,直接使用std::invoke(並使用std::void_t而不是voidify,儘管我喜歡後者的聲音)。

+0

這看起來很有希望。我想我可以強制callable_slot從較少參數的函數構造,然後我有它。謝謝! – rubenvb

+0

嗯,它似乎像[這個解決方案不處理成員函數指針](http://coliru.stacked-crooked.com/a/6dbcbf3f1e33c5b4):(讓我們看看我是否可以強迫它這樣做... – rubenvb

+0

@rubenvb查看編輯 –

1

不確定明白你到底想要什麼,但是...與std::tuplestd::make_index_sequence ...

首先你需要一個類型的特質,給你的函數的參數(或std::function

template <typename> 
struct numArgs; 

template <typename R, typename ... Args> 
struct numArgs<R(*)(Args...)> 
     : std::integral_constant<std::size_t, sizeof...(Args)> 
{ }; 

template <typename R, typename ... Args> 
struct numArgs<std::function<R(Args...)>> 
     : std::integral_constant<std::size_t, sizeof...(Args)> 
{ }; 

接下來的數量,您必須在callable_slot添加constexpr值記憶中的參數個數該Callable功能

static constexpr std::size_t numA { numArgs<Callable>::value }; 

然後,你必須修改call()方法來包裝參數的std::tuple<ArgTypes...>,並呼籲通過塗另一種方法PLE和0的索引序列numA

void call(ArgTypes... args) const override 
{ callI(std::make_tuple(args...), std::make_index_sequence<numA>{}); } 

最後你必須調用,在CallI()callable()功能只的參數

template <std::size_t ... Is> 
void callI (std::tuple<ArgTypes...> const & t, 
      std::index_sequence<Is...> const &) const 
{ callable(std::get<Is>(t)...); } 

元組的第一numA元素下面是一個全工作示例

#include <memory> 
#include <vector> 
#include <iostream> 
#include <functional> 

template <typename> 
struct numArgs; 

template <typename R, typename ... Args> 
struct numArgs<R(*)(Args...)> 
    : std::integral_constant<std::size_t, sizeof...(Args)> 
{ }; 

template <typename R, typename ... Args> 
struct numArgs<std::function<R(Args...)>> 
    : std::integral_constant<std::size_t, sizeof...(Args)> 
{ }; 

template <typename ... ArgTypes> 
struct slot 
{ 
    virtual ~slot() = default; 

    virtual void call(ArgTypes...) const = 0; 
}; 

template <typename Callable, typename ... ArgTypes> 
struct callable_slot : slot<ArgTypes...> 
{ 
    static constexpr std::size_t numA { numArgs<Callable>::value }; 

    callable_slot(Callable callable) : callable(callable) 
    { } 

    template <std::size_t ... Is> 
    void callI (std::tuple<ArgTypes...> const & t, 
       std::index_sequence<Is...> const &) const 
    { callable(std::get<Is>(t)...); } 

    void call(ArgTypes... args) const override 
    { callI(std::make_tuple(args...), std::make_index_sequence<numA>{}); } 

    Callable callable; 
}; 

template <typename ... ArgTypes> 
struct signal 
{ 
    template <typename Callable> 
    void connect(Callable callable) 
    { 
     slots.emplace_back(
     std::make_unique<callable_slot<Callable, ArgTypes...>>(callable)); 
    } 

    void emit(ArgTypes... args) 
    { for(const auto& slot : slots) slot->call(args...); } 

    std::vector<std::unique_ptr<slot<ArgTypes...>>> slots; 
}; 

void f (int i, char c) 
{ std::cout << "--- f(" << i << ", " << c << ")" << std::endl; } 

void g (int i) 
{ std::cout << "--- g(" << i << ")" << std::endl; } 

struct foo 
{ 
    static void j (int i, char c) 
    { std::cout << "--- j(" << i << ", " << c << ")" << std::endl; } 

    void k (int i) 
    { std::cout << "--- k(" << i << ")" << std::endl; } 
}; 

int main() 
{ 
    std::function<void(int, char)> h { [](int i, char c) 
     { std::cout << "--- h(" << i << ", " << c << ")" << std::endl; } 
    }; 

    std::function<void(int)> i { [](int i) 
     { std::cout << "--- i(" << i << ")" << std::endl; } 
    }; 

    using std::placeholders::_1; 

    foo foo_obj{}; 

    std::function<void(int)> k { std::bind(&foo::k, foo_obj, _1) }; 

    signal<int, char> s; 

    s.connect(f); 
    s.connect(g); 
    s.connect(h); 
    s.connect(i); 
    s.connect(foo::j); 
    s.connect(k); 

    s.emit(42, 'c'); 
} 

這個例子需要C++ 14由於採用std::make_index_sequencestd::index_sequence

替換它們並準備符合C++ 11的解決方案並不是很困難。

+0

這似乎是有限的,我需要很多額外的代碼來處理lambda和成員函數,或者我錯了嗎? – rubenvb

+0

@rubenvb - A很多額外的代碼?不,我不瘦k所以。在我看來,爲'std :: function'添加一個'numArgs'專用就足夠了。真正的問題是,lambda函數沒有一個真正的類型(任何lambda fanction都是一個類型);所以你需要將它們包裝在'std :: function's中(這對於struct/classes的靜態方法,對於'std :: bind'也是有用的);對於struct/classes的靜態方法,代碼可以毫無問題地工作。爲lambda和方法修改的答案(帶示例)。 – max66

+0

我不希望'std :: function'的開銷或任何'std :: bind'類的東西。請注意,我擁有的'slot'(完整的東西,不是這個有限的例子)幾乎表現爲可複用的'std :: function'(可重新調用成員函數指針及其被調用的對象)。 – rubenvb

相關問題