我目前有一个系统可以“连接”signal
函数。这signal
是一个可变参数模板,它可以将函数的参数作为模板参数 connect
到。
在当前的实现中,我显然无法连接到参数与 signal
不完全相同(或可以转换为)的函数。的参数。现在,当我试图模仿 Qt 的 signal
时/slot
/connect
, 我还想连接一个 signal
的 N
slot
的参数的 M<N
参数,这是完全明确定义的(即忽略信号的 >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
左值可调用项。如果你有 C++17,直接使用 std::invoke
(并使用 std::void_t
而不是 voidify
,虽然我喜欢后者的声音)。
关于c++ - 有没有办法部分匹配可变参数模板参数包?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43711871/