[英]Is there a way to partially match a variadic template parameter pack?
我目前有一個系統將signal
“連接”到功能。 此signal
是一個可變參數模板,它具有作為模板參數的可connect
函數的參數。
在當前的實現中,我顯然無法連接到參數不完全相同的函數(或者可以轉換為的函數)作為signal
的參數。 現在,當我試圖模仿Qt的signal
/ slot
/ connect
,我還想將N
參數的signal
連接到M<N
參數的slot
,這是完全明確定義的(即忽略>M
參數)信號,只是將第一個M傳遞給連接函數)。 有關我最簡單形式的代碼示例,請參閱Coliru 。
所以問題是雙重的:
connect
調用適用於函數void g(int);
? emit
調用適用於函數void g(int);
? 我猜我必須為slot
及其調用函數制作一些“魔術”參數包減速器,但是我看不出它們應該如何組合在一起所以實際上很難開始嘗試編寫解決方案。 如果至少Clang / GCC和Visual Studio 2015可以編譯它,我可以使用僅限C ++ 17的解決方案。
上面鏈接的代碼是為了完整性:
#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');
}
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
for lvalue callables。 如果你有C ++ 17,只需直接使用std::invoke
(並使用std::void_t
而不是voidify
,盡管我喜歡后者的聲音)。
不知道你究竟想要什么但是......用std::tuple
和std::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...>
打包參數,並調用另一個方法將元組和索引序列從0傳遞給numA
void call(ArgTypes... args) const override
{ callI(std::make_tuple(args...), std::make_index_sequence<numA>{}); }
最后你必須在CallI()
中callable()
函數,它只包含參數元組的第一個numA
元素
template <std::size_t ... Is>
void callI (std::tuple<ArgTypes...> const & t,
std::index_sequence<Is...> const &) const
{ callable(std::get<Is>(t)...); }
以下是一個完整的工作示例
#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_sequence
和std::index_sequence
。
替換它們並准備符合C ++ 11的解決方案並不是很困難。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.