Вывод нескольких пакетов параметров - PullRequest
0 голосов
/ 04 сентября 2018

Фон

Я пытаюсь написать некоторые шаблонные функции для библиотеки модульных тестов только для шаблона, специально для Qt.

Проблема

В этой библиотеке у меня есть шаблон переменной, который получает переменное количество объектов и функторов (фактически, сигналов Qt5), всегда спаренных рядом друг с другом, как в QObject, signal, etc..., а затем желательно с переменным количеством аргументов сигнала.

Желаемое решение

// implementation.h

template <typename T, typename U, typename... Sargs, typename... Fargs>
void test_signal_daisy_chain(T* t,  void(T::*t_signal)(Fargs...), 
                             U* u,  void(U::*u_signal)(Fargs...), 
                             Sargs... sargs, 
                             Fargs... fargs) {...}

// client.cpp

test_signal_daisy_chain(object, &Object::signal1, 
                        object, &Object::signal2, 
                        object, &Object::signal3, 
                        1, 2, 3); // where the signals are defined as void(Object::*)(int, int, int)

Где Fargs... соответствует как параметрам в t_signal и u_signal, так и аргументам, передаваемым в эту функцию для тестирования, а Sargs... соответствует переменному количеству QObject и функциям-членам сигнала (void(T::*)(Fargs...)) для эмиссии для явной цели тестирования.

Неудивительно, что я получаю «не совпадающую функцию» из-за «вычета / замены аргумента шаблона», и мой плагин ClangCodeModel предупреждает меня о том, что ожидается 6 аргументов, где было дано 8.

Рабочий (некрасивый) раствор

// implementation.h
template <typename... Fargs>
struct wrapper
{
    template <typename T, typename U, typename... Sargs>
    void test_signal_daisy_chain(Fargs... fargs, 
                                 T* t,  void(T::*t_signal)(Fargs...), 
                                 U* u,  void(U::*u_signal)(Fargs...), 
                                 Sargs... sargs) {...}

// client.cpp

wrapper<int, int, int>::test_signal_daisy_chain(1, 2, 3, 
                                                object, &Object::signal1,
                                                object, &Object::signal2,
                                                object, &Object::signal3);

Меня не устраивает необходимость явного определения аргументов переменной функции как в начале вызова функции, так и в параметрах типа шаблона оболочки. На самом деле, я был изначально удивлен, что нельзя было сделать вывод просто из-за того, что они должны были соответствовать переменным аргументам функторов. Я открыт для использования функций-оболочек, в отличие от классов-оболочек, поскольку у меня уже есть детализированное пространство имен, для которого я хочу получить беспорядок, чтобы обеспечить чистый и удобный API.

Примечание: аргументы сигнала могут быть где угодно: от примитивов до пользовательских типов, от структур POD до шаблонных классов, все переменной длины.

Редактировать 1 : c ++ 11 является жестким требованием, поэтому вы можете оставить в своем ответе функции> c ++ 11, если у них есть обходной путь c ++ 11 , т.е. auto... легко исправить, auto myFunction = []() constexpr {...}; гораздо меньше. Если использование if constexpr вместо рекурсивной вспомогательной функции template <std::size_t> экономит место и обеспечивает более краткий, полный и ориентированный на будущее ответ, тогда, пожалуйста, выберите тот стандарт, который вы считаете лучшим.

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

Самый простой подход - это упаковать параметры в кортеж в начале и передать кортеж в test_signal_daisy_chain_impl:

template < typename... Fargs, 
          typename T, typename... Sargs>
void test_signal_daisy_chain_impl(const std::tuple<Fargs...> & fargs, 
                                  T* t, void(T::*t_signal)(Fargs...),
                                  Sargs &&... sargs)
{
    // apply unpacks the tuple
    std::apply([&](auto ...params) 
               {
                   (t->*t_signal)(params...);
               }, fargs);

    // Although packed into the tuple, the elements in
    // the tuple were not removed from the parameter list,
    // so we have to ignore a tail of the size of Fargs.
    if constexpr (sizeof...(Sargs) > sizeof...(Fargs))
       test_signal_daisy_chain_impl(fargs, std::forward<Sargs>(sargs)...);
}

// Get a tuple out of the last I parameters
template <std::size_t I, typename Ret, typename T, typename... Qargs>
Ret get_last_n(T && t, Qargs && ...qargs)
{
    static_assert(I <= sizeof...(Qargs) + 1, 
                  "Not enough parameters to pass to the signal function");
    if constexpr(sizeof...(Qargs)+1  == I)
       return {std::forward<T>(t), std::forward<Qargs>(qargs)...};
    else
       return get_last_n<I, Ret>(std::forward<Qargs>(qargs)...);
}    

template <typename T, typename... Fargs, 
          typename... Qargs>
void test_signal_daisy_chain(T* t, void(T::*t_signal)(Fargs...),
                             Qargs&&... qargs)
{
    static_assert((sizeof...(Qargs) - sizeof...(Fargs)) % 2 == 0,
                  "Expecting even number of parameters for object-signal pairs");
    if constexpr ((sizeof...(Qargs) - sizeof...(Fargs)) % 2 == 0) {
        auto fargs = get_last_n<sizeof...(Fargs), std::tuple<Fargs...>>(
                                 std::forward<Qargs>(qargs)...);
        test_signal_daisy_chain_impl(fargs, t, t_signal, 
                                     std::forward<Qargs>(qargs)...);
    }
}

И использование:

class Object {
public:
    void print_vec(const std::vector<int> & vec)
    {
        for (auto elem: vec) std::cout << elem << ", ";
    }
    void signal1(const std::vector<int> & vec) 
    { 
        std::cout << "signal1(";
        print_vec(vec);
        std::cout << ")\n";
    }
    void signal2(const std::vector<int> & vec) 
    { 
        std::cout << "signal2(";
        print_vec(vec);
        std::cout << ")\n";
    }
    void signal_int1(int a, int b) 
    { std::cout << "signal_int1(" << a << ", " << b << ")\n"; }
    void signal_int2(int a, int b) 
    { std::cout << "signal_int2(" << a << ", " << b << ")\n"; }
    void signal_int3(int a, int b) 
    { std::cout << "signal_int3(" << a << ", " << b << ")\n"; }
};

int main()
{
   Object object;
   test_signal_daisy_chain(&object, &Object::signal1,
                           &object, &Object::signal2 ,
                           std::vector{1,2,3});
   test_signal_daisy_chain(&object, &Object::signal_int1,
                           &object, &Object::signal_int2 ,
                           &object, &Object::signal_int3,
                           1,2);
}

Редактировать 1

Поскольку C ++ 11 является жестким ограничением, существует гораздо более уродливое решение, основанное на тех же принципах. Такие вещи, как std::apply и std::make_index_sequence должны быть реализованы. Перегрузка используется вместо if constexpr(....):

template <std::size_t ...I>
struct indexes
{
    using type = indexes;
};

template<std::size_t N, std::size_t ...I>
struct make_indexes
{
    using type_aux = typename std::conditional<
                    (N == sizeof...(I)),
                    indexes<I...>,
                    make_indexes<N, I..., sizeof...(I)>>::type;
    using type = typename type_aux::type;
};

template <typename Tuple, typename T, typename Method, std::size_t... I>
void apply_method_impl(
    Method t_signal, T* t, const Tuple& tup, indexes<I...>)
{
    return (t->*t_signal)(std::get<I>(tup)...);
}

template <typename Tuple, typename T, typename Method>
void apply_method(const Tuple & tup, T* t, Method t_signal)
{
      apply_method_impl(
        t_signal, t, tup,
        typename make_indexes<
             std::tuple_size<Tuple>::value>::type{});
}

template < typename... Fargs,  typename... Sargs>
typename std::enable_if<(sizeof...(Fargs) == sizeof...(Sargs)), void>::type 
test_signal_daisy_chain_impl(const std::tuple<Fargs...> & , 
                             Sargs &&...)
{}

template < typename... Fargs, 
          typename T, typename... Sargs>
void test_signal_daisy_chain_impl(const std::tuple<Fargs...> & fargs, 
                                  T* t, void(T::*t_signal)(Fargs...),
                                  Sargs &&... sargs)
{
    apply_method(fargs, t, t_signal);

    // Although packed into the tuple, the elements in
    // the tuple were not removed from the parameter list,
    // so we have to ignore a tail of the size of Fargs.
    test_signal_daisy_chain_impl(fargs, std::forward<Sargs>(sargs)...);
}

// Get a tuple out of the last I parameters
template <std::size_t I, typename Ret, typename T, typename... Qargs>
typename std::enable_if<sizeof...(Qargs)+1  == I, Ret>::type
get_last_n(T && t, Qargs && ...qargs)
{
    return Ret{std::forward<T>(t), std::forward<Qargs>(qargs)...};
}    

template <std::size_t I, typename Ret, typename T, typename... Qargs>
typename std::enable_if<sizeof...(Qargs)+1  != I, Ret>::type
get_last_n(T && , Qargs && ...qargs)
{
    static_assert(I <= sizeof...(Qargs) + 1, "Not enough parameters to pass to the singal function");
    return get_last_n<I, Ret>(std::forward<Qargs>(qargs)...);
}    

template <typename T, typename... Fargs, 
          typename... Qargs>
void test_signal_daisy_chain(T* t, void(T::*t_signal)(Fargs...),
                             Qargs&&... qargs)
{
    static_assert((sizeof...(Qargs) - sizeof...(Fargs)) % 2 == 0,
                  "Expecting even number of parameters for object-signal pairs");
    auto fargs = get_last_n<sizeof...(Fargs), std::tuple<Fargs...>>(
                             std::forward<Qargs>(qargs)...);
    test_signal_daisy_chain_impl(fargs, t, t_signal, 
                                     std::forward<Qargs>(qargs)...);
}

Редактировать 2

Можно избежать рекурсии во время выполнения, сохранив все параметры в кортеже. Следующий test_signal_daisy_chain_flat() делает именно это, сохраняя тот же интерфейс, что и test_signal_daisy_chain():

template <typename Fargs, typename Pairs, std::size_t ...I>
void apply_pairs(Fargs && fargs, Pairs && pairs, const indexes<I...> &)
{
    int dummy[] = {
        (apply_method(std::forward<Fargs>(fargs),
                      std::get<I*2>(pairs),
                      std::get<I*2+1>(pairs)),
         0)...
    };
    (void)dummy;
}
template <typename T, typename... Fargs, 
          typename... Qargs>
void test_signal_daisy_chain_flat(T* t, void(T::*t_signal)(Fargs...),
                                  Qargs&&... qargs)
{
    static_assert((sizeof...(Qargs) - sizeof...(Fargs)) % 2 == 0,
                  "Expecting even number of parameters for object-signal pairs");
    auto fargs = get_last_n<sizeof...(Fargs), std::tuple<Fargs...>>(
                             std::forward<Qargs>(qargs)...);
    std::tuple<T*, void(T::*)(Fargs...), const Qargs&...> pairs{
        t, t_signal, qargs...};
    apply_pairs(fargs, pairs,
                typename make_indexes<(sizeof...(Qargs) - sizeof...(Fargs))/2>
                ::type{});
}

Предостережения

  1. Не утверждается, что пары параметров совпадают. Компилятор просто не может скомпилироваться (возможно, глубоко в рекурсии).
  2. Типы параметров, передаваемых в функцию, выводятся из сигнатуры первой функции независимо от типов конечных параметров - конечные параметры преобразуются в требуемые типы.
  3. Все функции должны иметь одинаковую подпись.
0 голосов
/ 04 сентября 2018
template<class T>
struct tag_t { using type=T; };
template<class Tag>
using type_t = typename Tag::type;

template<class T>
using no_deduction = type_t<tag_t<T>>;

template <typename T, typename U, typename... Sargs, typename... Fargs>
void test_signal_daisy_chain(
  T* t, void(T::*t_signal)(Sargs...),
  U* u, void(U::*u_signal)(Fargs...),
  no_deduction<Sargs>... sargs,
  no_deduction<Fargs>... fargs)

Я предполагаю, что Fargs... в t_signal было опечаткой и должно было быть Sargs.

Если нет, то у вас проблемы. Не существует правила, согласно которому «более ранняя дедукция превосходит более позднюю дедукцию».

Одна вещь, которую вы можете сделать в , - это иметь функцию, возвращающую объект функции:

template <typename T, typename U, typename... Fargs>
auto test_signal_daisy_chain(
  T* t, void(T::*t_signal)(Fargs...),
  U* u, void(U::*u_signal)(Fargs...),
  no_deduction<Fargs>... fargs
) {
  return [=](auto...sargs) {
    // ...
  };
}

Тогда использование выглядит так:

A a; B b;
test_signal_daisy_chain( &a, &A::foo, &b, &B::bar, 1 )('a', 'b', 'c');

сделать это в возможно с помощью объекта функции, написанного вручную.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...