Is there a way to partially match a variation template parameter package?

I currently have a system for "plugging in" signal

functions. This signal

is a variational template that has as template parameters the arguments of the functions it can connect

to.

In the current implementation, I obviously cannot connect to functions whose arguments are not exactly the same (or those that can be converted) as parameters signal

. Now, when I try to simulate the Qt signal

/ slot

/ connect

, I would also like to connect the parameters signal

of N

the parameters slot

of M<N

that (ie ignore the signal parameters of the >M

signal and simply pass the first M function connected). For a sample code I have in its most simplified form, see Coliru .

So the question is twofold:

  • How do I make a call connect

    to a function void g(int);

    ?
  • How do I make a call emit

    to a function void g(int);

    ?

I suppose I will need to make some "magic" reducers of the parameter packages for slot

and its calling function, but I don't see how it should all fit together, so it is rather difficult to start trying to code a solution. I'm fine with a C ++ 17-only solution, if at least Clang / GCC and Visual Studio 2015 can compile it.

The code above is for completeness:

#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');
}

      

+3


source to share


2 answers


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)...); 
    }
};

      

Then in callable_slot

:

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

      


To support member pointer (this requires SFINAE-friendly std::result_of

), change const_lvalue_call_t

to



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

      

then change the actual call in try_call::call

to

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

      

This is a bad person std::invoke

for lvalue callables. If you have C ++ 17, just use std::invoke

directly (and use std::void_t

instead voidify

, although I like the sound of the latter).

+3


source


Not sure what you exactly want, but ... with std::tuple

and std::make_index_sequence

...

First of all you need type traits that give you the number of arguments to the function (or 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)>
 { };

      

Then you need to add the value constexpr

to callable_slot

to remember the number of arguments in the functionCallable

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

      

Then you need to change the method call()

to wrap the arguments in std::tuple<ArgTypes...>

and call another method passing the tuple and the index sequence from 0 tonumA

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

      



Finally, you must call the CallI()

function callable()

with only the first numA

elements of the argument tuple

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

      

Below is a complete working example

#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');
 }

      

This example needs C ++ 14 because use std::make_index_sequence

and std::index_sequence

.

Replacing both of them and preparing a C ++ 11 compliant solution isn't very difficult.

+1


source







All Articles