我能否根据其运算符()的签名专门化可变参数模板参数

Can I specialize a variadic template argument based on the signature of its operator()

本文关键字:参数 专门化 变参 运算符      更新时间:2023-10-16

假设我有一个这样的函数

template <typename... FunctionList>
void call_all (int i, float f, const FunctionList... function_list);
template <>
void call_all (int, float)
{
}

我想专门化它,如下所示:

template <typename HasIntArgument, typename... FL>
void call_all (int i, float f, const HasIntArgument & hia, const FL... list)
{
hia (i);
call_all (i, f, list...);
}
template <typename HasFloatArgument, typename... FL>
void call_all (int i, float f, const HasFloatArgument & hfa, const FL... list)
{
hfa (f);
call_all (i, f, list...);
}

换句话说,我希望这个函数,对于function_list中的每个类似函数的对象,确定它是可以用签名void(int)还是void(float)调用。(此列表中的任何内容都不能使用多个签名进行调用。

我希望它适用于原始函数指针、lambda 或任何具有合适operator()的东西。

我可以直接写一个合适的专业,还是我必须用特征类和SFINAE做一些奇怪的事情?

你可以做这样的事情:

#if 0 // C++17
template <typename F>
void dispatch(F func, int i, float f)
{
if constexpr (has_int_argument<F>::value) {
func(i);
} else {
func(f);
}
}
#else // C++11
template <typename F>
typename std::enable_if<has_int_argument<F>::value>::type
dispatch(F func, int i, float)
{
func(i);
}
template <typename F>
typename std::enable_if<!has_int_argument<F>::value>::type
dispatch(F func, int, float f)
{
func(f);
}
#endif    

template <typename... Fs>
void call_all (int i, float f, const Fs&... fs)
{
// (dispatch(fs, i, f), ...); // C++17
const int dummy[] = {0, (dispatch(fs, i, f), 0)...};
static_cast<void>(dummy);
}

具有适当的功能特征has_int_argument. 像这样:

template <typename ClassOrSig> struct funct_trait;
template <typename C>
struct funct_trait : funct_trait<decltype(&C::operator())> {};
template <typename C, typename Ret, typename ...Args>
struct funct_trait<Ret (C::*) (Args...)> : funct_trait<Ret(Args...)> {};
template <typename C, typename Ret, typename ...Args>
struct funct_trait<Ret (C::*) (Args...) const> : funct_trait<Ret(Args...)> {};
// &&, &, volatile, ... (C ellipsis)
template <typename Ret, typename ...Args>
struct funct_trait<Ret (*)(Args...)> : funct_trait<Ret(Args...)> {};
template <typename Ret, typename ...Args>
struct funct_trait<Ret (Args...)>
{
using sig_type = Ret(Args...);
using args_tuple = std::tuple<Args...>;
// ...
};
template <typename T>
using has_int_argument = std::is_same<std::tuple<int>,
typename funct_trait<T>::args_tuple>;

演示

template<class...Fs>struct overloaded:Fs...{
using Fs::operator()...;
};
template<class...Fs>
overloaded(Fs...)->overloaded<Fs...>;

以上在 C++14 中有点棘手,但实现无处不在。

namespace details {
struct secret_tag {};
struct secret_result {
template<class...Ts>
secret_tag operator()(Ts&&...) const;
};
template<class F>
using secret_tester = overloaded<std::decay_t<F>, secret_result>;
}      
template<class F, class Arg>
using match_arg_exactly = std::integral_constant<
bool,
!std::is_same<
details::secret_tag, 
std::result_of_t< details::secret_tester<F>(Arg) >
>{}
>;

现在我们可以询问给定的对象,如果它可以完全匹配特定的参数。

template <typename HasIntArgument>
void call_one(int i, float f, std::true_type, const HasIntArgument & hia)
{
hia (i);
}
template <typename HasFloatArgument>
void call_one(int i, float f, std::false_type, const HasFloatArgument& hia)
{
hia (f);
}
template <typename F>
void call_one(int i, float f, const F & hia)
{
call_one( i, f, match_arg_exactly<const F&, int>{}, hia );
}

我们使用它:

void call_all (int, float)
{}
template<class F, class...Fs>
void call_all (int i, float f, F const& f0, Fs const&...fs) {
call_one( i, f, f0 );
call_all(i, f, fs...); 
}

测试代码:

struct float_eater {
void operator()(float x)const{ std::cout<< "float "<<x<<"n"; }
};
struct int_eater {
void operator()(int x)const{ std::cout<< "int "<<x<<"n"; }
};
call_all( 42, 3.14, float_eater{}, int_eater{}, int_eater{} );

现场示例

c++14overloaded如下所示:

template<class...Fs>
struct overloaded;
template<class F0>
struct overloaded<F0>:F0 {
overloaded(F0 f0):F0(std::move(f0)) {}
using F0::operator();
};
template<class F0, class F1>
struct overloaded<F0, F1>: F0, F1 {
overloaded( F0 f0, F1 f1 ):F0(std::move(f0)), F1(std::move(f1)) {}
using F0::operator();
using F1::operator();
};
template<class F0, class...Fs>
struct overloaded<F0, Fs...>:
overloaded<F0, overloaded<Fs...>>
{
overloaded(F0 f0, Fs...fs):
F0(std::move(f0)),
overloaded<Fs...>( std::move(fs)... )
{}
};

我认为这对我们的目的来说已经足够了。 (更一般地说,你要么做一个二叉树,平衡与否(,并处理完美的转发,然后......等。