Background
我正在尝试为仅模板单元测试库编写一些模板函数,特别是针对 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>
帮助函数可以节省空间并提供更简洁、完整和面向未来的答案,那么请选择您认为最好的标准。
最简单的方法是在开始时将参数打包到一个元组中,然后将元组传递给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{});
}
注意事项:
- 不断言参数对匹配。编译器根本无法编译(可能是深层次的递归(。 传递给函数的参数
- 类型是从第一个函数的签名中推断出来的,而不管尾随参数的类型如何 - 尾随参数将转换为所需的类型。
- 所有函数都必须具有相同的签名。
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)
我认为t_signal
Fargs...
是一个错字,应该是Sargs
.
如果没有,你就有麻烦了。 没有"先扣除胜过后推"的规则。
在 c++14 中可以做的一件事是让函数返回函数对象:
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');
在 C++11 中,使用手动编写的函数对象可以执行此操作。