c++ - 有没有办法部分匹配可变参数模板参数包?

标签 c++ c++11 c++14 variadic-templates c++17

我目前有一个系统可以“连接”signal函数。这signal是一个可变参数模板,它可以将函数的参数作为模板参数 connect到。

在当前的实现中,我显然无法连接到参数与 signal 不完全相同(或可以转换为)的函数。的参数。现在,当我试图模仿 Qt 的 signal 时/slot/connect , 我还想连接一个 signalN slot 的参数的 M<N参数,这是完全明确定义的(即忽略信号的 >M 参数,只将第一个 M 传递给连接的函数)。有关我最简单形式的代码示例,请参阅 Coliru .

所以这个问题有两个方面:

  1. 如何制作 connect为函数调用工作 void g(int);
  2. 如何制作 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/

相关文章:

c++ - 使用 Qt 工具栏和操作释放分配的内存

c++ - 将 std::map<int, vector<int>> 复制到 std::map<std:string, vector<int>>

c++ - `new auto` 有什么作用?

c++ - 为什么 unique_ptr 有两个函数 reset 和 operator= 做类似的事情但不重载?

c++ - 为什么这段代码会破坏内存?

c++ - 移动构造函数和继承

c++ - std::async 究竟是如何执行的?

c++ - 与可选方法的接口(interface)

c++ - 实现扩展内省(introspection)交换算法

c++ - 我在使用 bool 函数时遇到问题,用户输入两个字母并输出哪个字母在字母表中最高